home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Libraries / CPEditText 1.2 / CPEditText.cp < prev    next >
Text File  |  1993-10-03  |  101KB  |  3,520 lines

  1. /******************************************************************************
  2.  CPEditText.cp
  3.  
  4.                                The PEditText Class
  5.      
  6.     SUPERCLASS = CAbstractText
  7.     
  8.                              ---- DESCRIPTION ----
  9.  
  10.     CPEditText is a class for version 1.1.x of Symantec's THINK Class
  11.     Library that implements a simple text editing pane.  It can be used as
  12.     a direct replacement for the standard TCL CEditText class, provided
  13.     that the word wrapping and alignment features of CEditText are not used.
  14.     However, since CPEditText does not use the standard Macintosh TextEdit
  15.     routines, it supports fixed-width tabs and can be used to display and
  16.     edit more than 32k of text.
  17.     
  18.                              ---- LIMITATIONS ----
  19.     
  20.     CPEditText is designed to implement an MPW-style text editor, and as
  21.     such it supports only a single font, size and style for text and does
  22.     not support word-wrapping or alignment.  The code also does not use
  23.     the Macintosh Script Manager routines, so it may not work properly
  24.     with certain international versions of System software.  A future
  25.     release of this source code may address some or all of these
  26.     limitations.
  27.     
  28.                            ---- VERSION HISTORY ----
  29.     
  30.         • 1.0b1 (27 April 1992)
  31.             - Initial public release
  32.             
  33.         • 1.0b2 (30 April 1992)
  34.             - Fixed cosmetic bug in DoClick that caused insertion caret
  35.               to not get erased when selection range changed
  36.     
  37.         • 1.1b1 (17 October 1992)
  38.             - Added support for insertion buffer ("gap")
  39.             - Added IViewTemp, IPEditTextX, and CreateEnvironment methods
  40.         
  41.         • 1.1b2 (24 November 1992)
  42.             - Added CheckInsertion method to "preflight" insertions
  43.             - Added InsertText method and modified InsertTextPtr and
  44.               ReplaceSelection methods to bottleneck through InsertText
  45.             - Added UseTextHandle method
  46.             - Added CalcLineHeight and CalcTabWidth methods
  47.             - Created CPEditTextX.h header file for constants and macros
  48.         
  49.         • 1.1 (10 December 1992)
  50.             - Added typecasts to enable code to be compiled with "check
  51.               pointer types" option on
  52.         
  53.         • 1.2b1 (21 May 1993)
  54.             - Changes to compile under Symantec C++/TCL 1.1.3
  55.             - Added HideSelection, ScrollToSelection, ScrollToOffset and
  56.               GetChar methods
  57.             - Now check that pane is visible in Dawdle method
  58.             - Now scroll when extending selection via Shift-up/down arrow
  59.             - Fixed various minor bugs
  60.         
  61.         • 1.2b2 (2 June 1993)
  62.             - Modified Draw, Activate, Deactivate and HideSelection methods
  63.               to inhibit display of caret for noneditable text
  64.         
  65.         • 1.2b3 (4 June 1993)
  66.             - Removed hook instance variables and class methods and replaced
  67.               them with virtual hook methods
  68.         
  69.         • 1.2b4 (28 August 1993)
  70.             - Added DeleteText and CountRangeCRs methods
  71.             - Changed behavior of DoArrowKey method to conform to Apple's
  72.               Human Interface Guidelines
  73.         
  74.         • 1.2 (9 September 1993)
  75.             - Fixed bugs in Activate, Deactivate and TypeKey methods
  76.     
  77.                           ---- LICENSE AGREEMENT ----
  78.     
  79.     This source code was written by and is the property of Christopher R.
  80.     Wysocki.  It may be freely distributed and used, in whole or in part, in
  81.     any software for the Macintosh, including but not limited to commercial,
  82.     shareware, freeware or private applications.  However, any software that
  83.     uses any part or all of this source code must include a copyright notice
  84.     indicating that part or all of this source code is used in the software. 
  85.     You are entitled to make modifications or improvements to any portion of
  86.     this source code; you may not, however, distribute modified versions of
  87.     this source code.  Bug reports, suggestions, or general comments
  88.     regarding this source code are encouraged; the author can be contacted at
  89.     the electronic mail addresses listed below.  This license agreement and
  90.     the copyright notice below must not be modified in any way and must be
  91.     included with all distributions of this source code at all times.
  92.     
  93.     Copyright © 1992-1993 Christopher R. Wysocki.  All rights reserved.
  94.     
  95.                       ---- ELECTRONIC MAIL ADDRESSES ----
  96.     
  97.         America Online:      AFA ChrisW                  (preferred)
  98.         CompuServe:          72010,1140
  99.         Internet:          wysocki@netcom.com          (preferred)
  100.                           afachrisw@aol.com
  101.                           72010.1140@compuserve.com
  102.  
  103.  ******************************************************************************/
  104.  
  105. /** Includes **/
  106.  
  107. #include "CPEditText.h"
  108. #include "CPEditTextX.h"
  109.  
  110. #include <CClipboard.h>
  111. #include <CScrollPane.h>
  112. #include <CTextEnvirons.h>
  113. #include <Constants.h>
  114. #include <Commands.h>
  115. #include <LongCoordinates.h>
  116. #include <Global.h>
  117. #include <TBUtilities.h>
  118. #include <TCLUtilities.h>
  119.  
  120. #if THINK_C
  121. #include <asm.h>
  122. #endif
  123. #include <GestaltEqu.h>
  124. #include <Palettes.h>
  125. #include <Script.h>
  126.  
  127. /** Constants **/
  128.  
  129. enum { 
  130.     kUnhideSelection = FALSE,
  131.     kHideSelection = TRUE,
  132.     kRefreshAllTextAfter = FALSE,
  133.     kRefreshOnlyLine = TRUE
  134. };
  135.  
  136. /** Global Variables **/
  137.  
  138. extern CBureaucrat    *gGopher;            // First in line to get commands
  139. extern CClipboard    *gClipboard;        // Copies and Pastes data        
  140. extern CursHandle    gIBeamCursor;        // I-beam cursor for text views        
  141. extern CursHandle    gWatchCursor;        // Watch cursor for waiting    
  142. extern short        gClicks;            // Click counter                
  143. extern RgnHandle    gUtilRgn;            // Utility region                
  144.  
  145. /** Class Variables **/
  146.  
  147. CursHandle    CPEditText::cItalicIBeamCursor = NULL;
  148. RgnHandle    CPEditText::cSaveClipRgn = NULL;
  149.  
  150. /** Local Prototypes **/
  151.  
  152. static long        CountCRs(Ptr textP, long numChars);
  153. static Boolean    GetGrayRGBColor(const Rect *localRect, RGBColor *grayColor, RGBColor *prevForeColor);
  154. static Boolean    GestaltHasAttr(OSType selector, short responseBit);
  155.  
  156.  
  157. /**** C O N S T R U C T I O N / D E S T R U C T I O N   M E T H O D S ****/
  158.  
  159.  
  160. /******************************************************************************
  161.  IPEditText
  162.  
  163.         Initialize a PEditText object.
  164.  ******************************************************************************/
  165.  
  166. void    CPEditText::IPEditText(
  167.     CView            *anEnclosure,
  168.     CBureaucrat        *aSupervisor,
  169.     short            aWidth,
  170.     short            aHeight,
  171.     short            aHEncl,
  172.     short            aVEncl,
  173.     SizingOption    aHSizing,
  174.     SizingOption    aVSizing)
  175. {    
  176.     CAbstractText::IAbstractText(anEnclosure, aSupervisor, aWidth, aHeight,
  177.                          aHEncl, aVEncl, aHSizing, aVSizing, kDefaultBoundsWidth);
  178.     IPEditTextX();
  179. }
  180.  
  181.  
  182. /******************************************************************************
  183.  IViewTemp {OVERRIDE}
  184.  
  185.         Initialize a PEditText object from a resource template.
  186.  ******************************************************************************/
  187.  
  188. void    CPEditText::IViewTemp(CView *anEnclosure, CBureaucrat *aSupervisor, Ptr viewData)
  189. {
  190.     inherited::IViewTemp(anEnclosure, aSupervisor, viewData);
  191.     IPEditTextX();
  192. }
  193.  
  194.  
  195. /******************************************************************************
  196.  IPEditTextX
  197.  
  198.         Extra initialization for a PEditText object.  Called by IPEditText
  199.         and IViewTemp.
  200.  ******************************************************************************/
  201.  
  202. void    CPEditText::IPEditTextX()
  203. {
  204.     UseLongCoordinates(TRUE);
  205.     SetWholeLines(TRUE);
  206.     
  207.     // Initialize instance variables
  208.     itsTextHandle = NewHandle(0);
  209.     itsTextLength = 0;
  210.     itsNumLines = 1;
  211.     itsLineStarts = (LongHandle)NewHandle(sizeof(long));
  212.     **itsLineStarts = 0;
  213. #if qPEUseInsertionGap
  214.     itsGapPosition = itsGapLength = 0;
  215. #endif
  216.         
  217.     itsTextFont = GetAppFont();
  218.     itsTextSize = GetDefFontSize();
  219.     itsTextFace = normal;
  220.     itsTextMode = srcOr;
  221.     itsTabSpaces = kDefaultTabSpaces;
  222.     itsSpacingCmd = cmdSingleSpace;
  223.     
  224.     itsSelStart = itsSelEnd = itsSelAnchor = 0;
  225.     fOutlineHilite = FALSE;
  226.     fUseItalicCaret = FALSE;
  227.     fShowInvisibles = FALSE;
  228.     itsClickTime = itsCaretTime = 0;
  229.     fCaretVisible = FALSE;
  230.     fReallyActive = FALSE;
  231.     fUpDownArrow = FALSE;
  232.     itsUpDownHOffset = 0;
  233. #if !qTCL113
  234.     scrollHoriz = FALSE;
  235. #endif
  236.         
  237.     CreateEnvironment();
  238.     
  239.     // Initialize class variables
  240.     cItalicIBeamCursor = GetCursor(rItalicIBeamCursor);
  241.     if (cItalicIBeamCursor != NULL)
  242.         HNoPurge((Handle)cItalicIBeamCursor);
  243.     if (cSaveClipRgn == NULL)
  244.         cSaveClipRgn = NewRgn();
  245. }
  246.  
  247.  
  248. /******************************************************************************
  249.  CreateEnvironment
  250.  
  251.         Create and initialize the text environment.
  252.  ******************************************************************************/
  253.  
  254. void    CPEditText::CreateEnvironment()
  255. {
  256.     CTextEnvirons    *textEnvirons;
  257.     TextInfoRec        textInfo;
  258.     
  259.     textEnvirons = new CTextEnvirons;
  260.     textEnvirons->ITextEnvirons();
  261.     
  262.     textInfo.fontNumber = itsTextFont;
  263.     textInfo.theSize = itsTextSize;
  264.     textInfo.theStyle = itsTextFace;
  265.     textInfo.theMode = itsTextMode;
  266.     
  267.     textEnvirons->SetTextInfo(&textInfo);
  268.     itsEnvironment = textEnvirons;
  269.     
  270.     CalcLineHeight();
  271. }
  272.  
  273.  
  274. /******************************************************************************
  275.  Dispose {OVERRIDE}
  276.  
  277.         Dispose of a PEditText object.
  278.  ******************************************************************************/
  279.  
  280. void    CPEditText::Dispose()
  281. {
  282.     ForgetHandle(itsTextHandle);
  283.     ForgetHandle(itsLineStarts);
  284.     inherited::Dispose();
  285. }
  286.  
  287.  
  288. /**** D I S P L A Y   M E T H O D S ****/
  289.     
  290.  
  291. /******************************************************************************
  292.  Draw {OVERRIDE}
  293.  
  294.         Draw the contents of the PEditText pane.
  295.  ******************************************************************************/
  296.  
  297. void    CPEditText::Draw(Rect *area)
  298. {
  299.     LongRect    longArea;
  300.     long        startLine;
  301.     long        endLine;
  302.     long        vertInset = VertInset();
  303.     
  304.     // Compute and draw the visible text lines
  305.     QDToFrameR(area, &longArea);
  306.     startLine = (longArea.top - vertInset) / itsLineHeight;
  307.     endLine = (longArea.bottom - vertInset) / itsLineHeight;
  308.     endLine = Min(endLine, itsNumLines - 1);
  309.     DrawLineRange(startLine, endLine, 0, kDontEraseText);
  310.     
  311.     // If printing is not in progress, highlight the current selection
  312.     // or draw the insertion caret, as appropriate
  313.     if (!this->printing && this->wantsClicks && (fReallyActive || fOutlineHilite)) {
  314.         if (itsSelStart != itsSelEnd)
  315.             HiliteTextRange(itsSelStart, itsSelEnd);
  316.         else if (fCaretVisible)
  317.             DrawCaret();
  318.     }
  319. }
  320.  
  321.  
  322. /******************************************************************************
  323.  Activate {OVERRIDE}
  324.  
  325.         Activate a PEditText pane by hiliting the selection or showing
  326.         the text insertion caret.
  327.  ******************************************************************************/
  328.  
  329. void    CPEditText::Activate()
  330. {
  331.     Boolean        wasActive = fReallyActive;
  332.     
  333.     // Remove the outlined selection range or erase the inactive caret
  334.     if (!wasActive)
  335.         HideSelection(kHideSelection, kRedraw);
  336.     
  337.     // Set instance variable to indicate that we're really active
  338.     fReallyActive = TRUE;
  339.     
  340.     // Call the inherited method to activate the pane
  341.     inherited::Activate();
  342.     
  343.     // Hilite the now active selection range, if appropriate
  344.     if (!wasActive && this->wantsClicks && (this->editable || (itsSelStart != itsSelEnd)))
  345.         HideSelection(kUnhideSelection, kRedraw);
  346. }
  347.  
  348.  
  349. /******************************************************************************
  350.  Deactivate {OVERRIDE}
  351.  
  352.         Deactivate a PEditText pane by unhiliting the selection or hiding
  353.         the text insertion caret.
  354.  ******************************************************************************/
  355.  
  356. void    CPEditText::Deactivate()
  357. {
  358.     Boolean        wasActive = fReallyActive;
  359.     
  360.     // Unhilite the selection range or erase the insertion caret
  361.     if (wasActive)
  362.         HideSelection(kHideSelection, kRedraw);
  363.     
  364.     // Set instance variable to indicate that we're not really active
  365.     fReallyActive = FALSE;
  366.     
  367.     // Call the inherited method to deactivate the pane
  368.     inherited::Deactivate();
  369.     
  370.     // Outline the now inactive selection range, if appropriate
  371.     if (wasActive && fOutlineHilite && this->wantsClicks && (this->editable || (itsSelStart != itsSelEnd)))
  372.         HideSelection(kUnhideSelection, kRedraw);
  373. }
  374.  
  375.  
  376. /******************************************************************************
  377.  SetSelection {OVERRIDE}
  378.  
  379.          Sets the selected text to the range corresponding to character
  380.          positions selStart through selEnd.
  381.  ******************************************************************************/
  382.  
  383. void    CPEditText::SetSelection(long selStart, long selEnd, Boolean redraw)
  384. {
  385.     // Ensure that the positions are valid
  386.     selStart = Max(selStart, 0);
  387.     selStart = Min(selStart, itsTextLength);
  388.     selEnd = Max(selEnd, 0);
  389.     selEnd = Min(selEnd, itsTextLength);
  390.     selEnd = Max(selEnd, selStart);
  391.     
  392.     // Take a quick exit if the selection range isn't being changed
  393.     if ((selStart == itsSelStart) && (selEnd == itsSelEnd))
  394.         return;
  395.     
  396.     // Unhilite the old selection range
  397.     HideSelection(kHideSelection, redraw);
  398.     
  399.     // Update our instance variables
  400.     itsSelStart = selStart;
  401.     itsSelEnd = selEnd;
  402.     fUpDownArrow = FALSE;
  403.     
  404.     // Ensure that the anchor point is one of the selection endpoints
  405.     if ((itsSelAnchor != selStart) && (itsSelAnchor != selEnd))
  406.         itsSelAnchor = selStart;
  407.         
  408.     // Hilite the new selection range
  409.     if (this->editable || (selStart != selEnd))
  410.         HideSelection(kUnhideSelection, redraw);
  411. }
  412.  
  413.  
  414. /******************************************************************************
  415.  HideSelection {OVERRIDE}
  416.  
  417.         Hide/unhide the current selection and the blinking cursor.
  418.         Does not change the active or gopher state.
  419.  ******************************************************************************/
  420.  
  421. void    CPEditText::HideSelection(Boolean hide, Boolean redraw)
  422. {    
  423.     if (redraw) {
  424.         Prepare();
  425.         if (itsSelStart != itsSelEnd)
  426.             HiliteTextRange(itsSelStart, itsSelEnd);
  427.         else {
  428.             if (hide)
  429.                 HideCaret();
  430.             else
  431.                 ShowCaret();
  432.         }
  433.     }
  434. }    
  435.  
  436.  
  437. /******************************************************************************
  438.  ScrollToSelection {OVERRIDE}
  439.  
  440.         Scroll the text so that the current selection is visible within
  441.         the frame.  This method will scroll the text so that the selection
  442.         is in the middle of the frame.
  443.  ******************************************************************************/
  444.         
  445. void    CPEditText::ScrollToSelection()
  446. {
  447.     long    startLine, endLine;
  448.     short    hSpan, vSpan;
  449.     LongPt    selPos, selPt;
  450.     long    selStart, selEnd;
  451.     
  452.     // Calculate the number of panorama units spanned by the frame
  453.     GetFrameSpan(&hSpan, &vSpan);
  454.     
  455.     // Determine the starting and ending lines for the selection
  456.     GetSelection(&selStart, &selEnd);
  457.     startLine = FindLine(selStart);
  458.     endLine = (selStart == selEnd ? startLine : FindLine(selEnd));
  459.     
  460.     // Calculate the new vertical scroll position
  461.     if (startLine >= position.v + vSpan) {
  462.         selPos.v = startLine - (vSpan / 2) + 1;
  463.         selPos.v = Max(selPos.v, 0);
  464.         selPos.v = Min(selPos.v, itsNumLines - vSpan);
  465.     }
  466.     else if (endLine < position.v) {
  467.         selPos.v = endLine - (vSpan / 2) + 1;
  468.         selPos.v = Max(selPos.v, 0);
  469.         selPos.v = Min(selPos.v, itsNumLines - vSpan);
  470.     }
  471.     else
  472.         selPos.v = position.v;
  473.     
  474.     // Calculate the new horizontal scroll position
  475.     if (!scrollHoriz)
  476.         selPos.h = position.h;
  477.     else {
  478.         GetCharPoint(selPos.v == startLine ? selStart : selEnd, &selPt);
  479.         selPos.h = (selPt.h / hScale) - (hSpan / 2);
  480.         selPos.h = Max(0, selPos.h);
  481.     }
  482.     
  483.     // Scroll the pane if necessary
  484.     if ((selPos.v != position.v) || (selPos.h != position.h))
  485.         ScrollTo(&selPos, kRedraw);
  486. }
  487.  
  488.  
  489. /******************************************************************************
  490.  ScrollToOffset
  491.  
  492.         Scroll the text so that the character at the given offset is visible
  493.         within the frame.  Unlike ScrollToSelection, this method will scroll
  494.         the text as little as is necessary to make the character visible.
  495.  ******************************************************************************/
  496.  
  497. void    CPEditText::ScrollToOffset(long charOffset)
  498. {
  499.     long    charLine;
  500.     short    hSpan, vSpan;
  501.     LongPt    selPos, selPt;
  502.     
  503.     // Calculate the number of panorama units spanned by the frame
  504.     GetFrameSpan(&hSpan, &vSpan);
  505.     
  506.     // Calculate the new vertical scroll position
  507.     charLine = FindLine(charOffset);
  508.     if (charLine >= position.v + vSpan)
  509.         selPos.v = charLine - vSpan + 1;
  510.     else if (charLine < position.v)
  511.         selPos.v = charLine;
  512.     else
  513.         selPos.v = position.v;
  514.     
  515.     // Calculate the new horizontal scroll position
  516.     if (!scrollHoriz)
  517.         selPos.h = position.h;
  518.     else {
  519.         GetCharPoint(charOffset, &selPt);
  520.         selPos.h = selPt.h / hScale;
  521.         if (selPos.h >= position.h + hSpan)
  522.             selPos.h -= (hSpan * 3) / 4;
  523.         else if (selPos.h < position.h)
  524.             selPos.h -= hSpan / 4;
  525.         else
  526.             selPos.h = position.h;
  527.         selPos.h = Max(selPos.h, 0);
  528.     }
  529.     
  530.     // Scroll the pane if necessary
  531.     if ((selPos.v != position.v) || (selPos.h != position.h))
  532.         ScrollTo(&selPos, kRedraw);
  533. }
  534.  
  535.  
  536. /**** C U R S O R   M E T H O D S ****/
  537.  
  538.  
  539. /******************************************************************************
  540.  AdjustCursor {OVERRIDE}
  541.  
  542.         Adjust the shape of the cursor according the position of the mouse.
  543.  ******************************************************************************/
  544.  
  545. void    CPEditText::AdjustCursor(Point where, RgnHandle mouseRgn)
  546. {    
  547.     if ((itsTextFace & italic) && fUseItalicCaret && (cItalicIBeamCursor != NULL))
  548.         SetCursor(*cItalicIBeamCursor);
  549.     else
  550.         SetCursor(*gIBeamCursor);
  551. }
  552.  
  553.  
  554. /******************************************************************************
  555.  Dawdle {OVERRIDE}
  556.  
  557.         The user isn't doing anything, so flash the text insertion caret.
  558.         Also check if the insertion gap needed to be repositioned and/or
  559.         resized.
  560.  ******************************************************************************/
  561.  
  562. void    CPEditText::Dawdle(long *maxSleep)
  563. {
  564.     long        ticks;
  565.     
  566.     if (editable && visible) {
  567.         
  568.         // Flash the insertion caret
  569.         if ((itsSelStart == itsSelEnd) && ((ticks = TickCount()) >= itsCaretTime)) {
  570.             Prepare();
  571.             DrawCaret();
  572.             fCaretVisible = !fCaretVisible;
  573.             itsCaretTime = ticks + GetCaretTime();
  574.             *maxSleep = GetCaretTime();
  575.         }
  576.         
  577. #if qPEUseInsertionGap
  578.         // Move and resize the insertion gap
  579.         SetGapPosition(itsSelStart);
  580.         if ((itsGapLength < kStandardGapLength / 2) || (itsGapLength > kStandardGapLength * 2))
  581.             SetGapLength(kStandardGapLength);
  582. #endif
  583.     }
  584. }
  585.     
  586.  
  587. /**** M O U S E   A N D   K E Y S T R O K E   M E T H O D S ****/
  588.  
  589.  
  590. /******************************************************************************
  591.  DoClick {OVERRIDE}
  592.  
  593.         Respond to a mouse click within the PEditText.  A single click
  594.         positions the insertion point, a double click selects a word, a
  595.         triple click selects a line or paragraph, and a quadruple click
  596.         selects all of the text.
  597.  ******************************************************************************/
  598.  
  599. void    CPEditText::DoClick(Point hitPt, short modifierKeys, long when)
  600. {     
  601.     LongPt        framePt;
  602.     LongPt        autoScrollPt;
  603.     long        selStart, selEnd;
  604.     long        charOffset, origCharOffset, lastCharOffset;
  605.     long        newSelStart, newSelEnd;
  606.     long        vertInset = VertInset();
  607.     
  608.     // Get the current selection range
  609.     GetSelection(&selStart, &selEnd);
  610.     
  611.     // Determine which character was clicked on
  612.     QDToFrame(hitPt, &framePt);
  613.     charOffset = GetCharOffset(&framePt);
  614.     origCharOffset = lastCharOffset = charOffset;
  615.     
  616.     // Determine the new selection range based on the number of clicks
  617.     if (gClicks == 1) {
  618.         // If the Shift key is down, extend the selection    
  619.         if (modifierKeys & shiftKey)
  620.             origCharOffset = lastCharOffset = itsSelAnchor;
  621.         else
  622.             SetSelection(charOffset, charOffset, kRedraw);    
  623.     }
  624.     else if (gClicks == 2) {
  625.         GetWordBounds(charOffset, &selStart, &selEnd);
  626.         SetSelection(selStart, selEnd, kRedraw);
  627.     }
  628.     else {
  629.         GetParagraphBounds(charOffset, &selStart, &selEnd);
  630.         
  631.         if ((itsSelStart != itsSelEnd) && (selStart < itsSelStart) && (selEnd > itsSelEnd)) {
  632.             HiliteTextRange(selStart, itsSelStart);
  633.             HiliteTextRange(itsSelEnd, selEnd);
  634.             SetSelection(selStart, selEnd, kNoRedraw);
  635.         }
  636.         else if ((selStart != itsSelStart) || (selEnd != itsSelEnd))
  637.             SetSelection(selStart, selEnd, kRedraw);
  638.     }
  639.     
  640.     // Ensure that the insertion caret is visible
  641.     if (selStart == selEnd)
  642.         ShowCaret();
  643.     
  644.     // Track the selection while the mouse is down
  645.     while (StillDown()) {
  646.         
  647.         // Get the current mouse position in frame coordinates
  648.         GetMouse(&hitPt);
  649.         QDToFrame(hitPt, &framePt);
  650.         autoScrollPt = framePt;
  651.         PinInRect(&frame, &framePt);
  652.         
  653.         // Check if the mouse has moved to a new character
  654.         charOffset = GetCharOffset(&framePt);
  655.         if (charOffset != lastCharOffset) {
  656.             
  657.             // Remember the current selection range
  658.             selStart = itsSelStart;
  659.             selEnd = itsSelEnd;
  660.             
  661.             // Determine the new selection range
  662.             if (gClicks == 1) {
  663.                 newSelStart = Min(origCharOffset, charOffset);
  664.                 newSelEnd = Max(origCharOffset, charOffset);
  665.             }
  666.             else if (gClicks == 2) {
  667.                 if (charOffset < origCharOffset) {
  668.                     newSelStart = WordBreakHook(charOffset, kBreakLeft);
  669.                     newSelEnd = WordBreakHook(origCharOffset, kBreakRight);
  670.                 }
  671.                 else {
  672.                     newSelStart = WordBreakHook(origCharOffset, kBreakLeft);
  673.                     newSelEnd = WordBreakHook(charOffset, kBreakRight);
  674.                 }
  675.             }
  676.             else {    // gClicks == 3
  677.                 if (charOffset < origCharOffset) {
  678.                     GetParagraphBounds(origCharOffset, NULL, &newSelEnd);
  679.                     GetParagraphBounds(charOffset, &newSelStart, NULL);
  680.                 }
  681.                 else {
  682.                     GetParagraphBounds(origCharOffset, &newSelStart, NULL);
  683.                     GetParagraphBounds(charOffset, NULL, &newSelEnd);
  684.                 }
  685.             }
  686.             
  687.             // Adjust the hilited text
  688.             if ((selStart == selEnd) && fCaretVisible && ((selStart != newSelStart) || (selStart != newSelEnd)))
  689.                 HideCaret();
  690.             if (selStart != newSelStart)
  691.                 HiliteTextRange(Min(selStart, newSelStart), Max(selStart, newSelStart));
  692.             if (selEnd != newSelEnd)
  693.                 HiliteTextRange(Min(selEnd, newSelEnd), Max(selEnd, newSelEnd));
  694.             
  695.             SetSelection(newSelStart, newSelEnd, kNoRedraw);
  696.             
  697.             if (newSelStart == newSelEnd)
  698.                 ShowCaret();
  699.             
  700.             // Remember the current character offset
  701.             lastCharOffset = charOffset;
  702.         }
  703.         
  704.         // Scroll the text automatically while the user is selecting text
  705.         AutoScroll(&autoScrollPt);
  706.     }
  707.     
  708.     // Determine the anchor point for the selection
  709.     if (gClicks == 1)
  710.         itsSelAnchor = origCharOffset;
  711.     else if (gClicks == 2)
  712.         itsSelAnchor = WordBreakHook(origCharOffset, charOffset < origCharOffset ? kBreakRight : kBreakLeft);
  713.     else {
  714.         GetParagraphBounds(origCharOffset, &selStart, &selEnd);
  715.         itsSelAnchor = (charOffset < origCharOffset ? selEnd : selStart);
  716.     }
  717.     
  718.     // For non-editable but selectable text, we want to display a selection
  719.     // range so the user can copy, but we don't want to display a caret
  720.     if (!this->editable && (itsSelStart == itsSelEnd))
  721.         HideCaret();
  722.     
  723.     // Notify our dependents that the selection has changed
  724.     SelectionChanged();
  725. }
  726.  
  727.  
  728. /******************************************************************************
  729.  HitSamePart {OVERRIDE}
  730.  
  731.         Check whether two points hit the same character in the text.
  732.         The points are in Window coordinates.
  733.  ******************************************************************************/
  734.  
  735. Boolean    CPEditText::HitSamePart(Point pointA, Point pointB)
  736. {
  737.     LongPt    framePtA, framePtB;
  738.     
  739.     WindToFrame(pointA, &framePtA);
  740.     WindToFrame(pointB, &framePtB);
  741.     
  742.     return (GetCharOffset(&framePtA) == GetCharOffset(&framePtB));
  743. }
  744.  
  745.  
  746. /******************************************************************************
  747.  DoKeyDown {OVERRIDE}
  748.  
  749.         Respond to a keystroke.
  750.  ******************************************************************************/
  751.  
  752. void    CPEditText::DoKeyDown(char theChar, Byte keyCode, EventRecord *macEvent)
  753. {
  754.     if (IsArrowKey(theChar) && (macEvent->modifiers & cmdKey)) {
  755.         DoArrowKey(theChar, macEvent->modifiers);
  756.         
  757.         // Notify our dependents that the selection has changed
  758.         // This used to be done in DoArrowKey, but we now do it here
  759.         // so the CTextEditTask::DoFwdDelete works properly
  760.         SelectionChanged();
  761.     }
  762.     else
  763.         inherited::DoKeyDown(theChar, keyCode, macEvent);
  764. }
  765.     
  766.  
  767. /******************************************************************************
  768.  TypeChar {OVERRIDE}
  769.  
  770.         Process a single keystroke. All undo setup has already been
  771.         performed, and the keystroke should be handled directly.
  772.  ******************************************************************************/
  773.  
  774. void    CPEditText::TypeChar(char theChar, short theModifiers)
  775. {
  776.     // Need to check for arrow keys here, even though we check in the
  777.     // DoKeyDown method, since CTextEditTask calls TypeChar directly
  778.     if (IsArrowKey(theChar))
  779.         DoArrowKey(theChar, theModifiers);
  780.     else {
  781.         long    selStart = itsSelStart;
  782.         long    selEnd = itsSelEnd;
  783.         
  784.         // Obscure the cursor so it is hidden until the mouse is moved
  785.         ObscureCursor();
  786.         
  787.         // Now perform the typing
  788.         if (theChar == kBackspace) {
  789.             if (selStart == selEnd && selStart > 0) {
  790.                 HideCaret();
  791.                 DeleteTextRange(selStart - 1, selStart, kRedraw);
  792.                 ShowCaret();
  793.             }
  794.             else
  795.                 DeleteTextRange(selStart, selEnd, kRedraw);
  796.         }
  797.         else {
  798.             if (selStart != selEnd)
  799.                 ReplaceSelection(&theChar, sizeof(char));
  800.             else
  801.                 InsertTextPtr(&theChar, sizeof(char), kNoRedraw);
  802.         }
  803.         
  804.         // Ensure that the insertion point is visible
  805.         ScrollToOffset(itsSelStart);
  806.     }
  807. }    
  808.  
  809.  
  810. /******************************************************************************
  811.  DoArrowKey
  812.  
  813.          Handle a cursor key.  The behavior is as follows:
  814.          
  815.                                modifier key
  816.                         <none>         Option         Command
  817.                          
  818.           up            prev line    prev page    first line
  819.           down        next line    next page    last line
  820.           left        prev char    prev word    line start
  821.           right        next char    next word    line end
  822.          
  823.  ******************************************************************************/
  824.  
  825. void    CPEditText::DoArrowKey(char theChar, short theModifiers)
  826. {
  827.     Boolean            commandKeyDown = ((theModifiers & cmdKey) != 0);
  828.     Boolean            optionKeyDown = ((theModifiers & optionKey) != 0);
  829.     Boolean            shiftKeyDown = ((theModifiers & shiftKey) != 0);
  830.     Boolean            isInsertion;
  831.     Boolean            isUpDownArrow;
  832.     register long    nonAnchor;
  833.     register long    selChar;
  834.     long            numLines;
  835.     long            selLine;
  836.     long            selStart;
  837.     long            selEnd;
  838.     long            textLength;
  839.     long            newStart;
  840.     long            newEnd;
  841.     LongPt            charPt;
  842.     
  843.     GetSelection(&selStart, &selEnd);
  844.     textLength = GetLength();
  845.     numLines = GetNumLines();
  846.     
  847.     // Determine if we have an insertion point or not
  848.     isInsertion = (selStart == selEnd);
  849.     
  850.     // Reset the selection anchor position if necessary
  851.     if (isInsertion)
  852.         itsSelAnchor = selStart;
  853.     else if (itsSelAnchor < 0) {
  854.         if ((theChar == kLeftCursor) || (theChar == kUpCursor))
  855.             itsSelAnchor = selEnd;
  856.         else
  857.             itsSelAnchor = selStart;
  858.     }
  859.     
  860.     // Determine the non-anchored selection position
  861.     nonAnchor = (itsSelAnchor == selEnd ? selStart : selEnd);
  862.     
  863.     // Now handle the keystroke
  864.     if ((theChar == kLeftCursor) || (theChar == kRightCursor)) {
  865.         if (commandKeyDown) {
  866.             selLine = FindLine(nonAnchor);
  867.             
  868.             if (theChar == kLeftCursor)
  869.                 selChar = GetLineStart(selLine);
  870.             else {
  871.                 selChar = GetLineEnd(selLine);
  872.                 if (selLine < numLines - 1)
  873.                     --selChar;
  874.             }
  875.         }
  876.         else if (optionKeyDown) {
  877.             Boolean expanding;
  878.  
  879.             if (shiftKeyDown) {
  880.                 if (theChar == kLeftCursor)
  881.                     expanding = (nonAnchor <= itsSelAnchor);
  882.                 else
  883.                     expanding = (nonAnchor >= itsSelAnchor);
  884.             }
  885.             else
  886.                 expanding = TRUE;
  887.             
  888.             selChar = nonAnchor;
  889.             if (!expanding) {
  890.                 if (theChar == kLeftCursor) {
  891.                     newEnd = selChar;
  892.                     while ((selChar > itsSelAnchor) && ((!GetWordBounds(selChar, &newStart, &newEnd)) || (newEnd + 1 >= nonAnchor)))
  893.                         selChar = newStart - 1;
  894.                     if (selChar <= itsSelAnchor) {
  895.                         expanding = TRUE;
  896.                         selChar = itsSelAnchor;
  897.                     }
  898.                     else
  899.                         selChar = newEnd;
  900.                 }
  901.                 else {
  902.                     newStart = selChar;
  903.                     while ((selChar < itsSelAnchor) && ((!GetWordBounds(selChar, &newStart, &newEnd)) || (newStart <= nonAnchor)))
  904.                         selChar = newEnd + 1;
  905.                     if (selChar >= itsSelAnchor) {
  906.                         expanding = TRUE;
  907.                         selChar = itsSelAnchor;
  908.                     }
  909.                 }
  910.             }
  911.             
  912.             if (expanding) {
  913.                 if (theChar == kLeftCursor) {
  914.                     long    minSelChar = selChar;
  915.                     
  916.                     newStart = selChar;
  917.                     while ((selChar > 0) && ((!GetWordBounds(selChar, &newStart, &newEnd)) || (newStart >= minSelChar)))
  918.                         --selChar;
  919.                     selChar = Min(selChar, newStart);
  920.                 }
  921.                 else {
  922.                     long    maxSelChar = textLength - 1;
  923.                     newEnd = selChar;
  924.                     while ((selChar < maxSelChar) && !GetWordBounds(selChar + 1, &newStart, &newEnd))
  925.                         ++selChar;
  926.                     selChar = Max(selChar, newEnd);
  927.                 }
  928.             }
  929.         }
  930.         else if (shiftKeyDown || isInsertion) {
  931.             if (theChar == kLeftCursor)
  932.                 selChar = nonAnchor - 1;
  933.             else
  934.                 selChar = nonAnchor + 1;
  935.             selChar = Min(Max(selChar, 0), textLength);
  936.         }
  937.         else
  938.             selChar = ((theChar == kLeftCursor) ? selStart : selEnd);
  939.         
  940.         isUpDownArrow = FALSE;
  941.     }
  942.     else {    // ((theChar == kUpCursor) || (theChar == kDownCursor))
  943.         
  944.         selLine = FindLine(nonAnchor);
  945.         
  946.         if ((theChar == kUpCursor) && (commandKeyDown || (selLine == 0))) {
  947.             selChar = 0;
  948.             isUpDownArrow = FALSE;
  949.         }
  950.         else if ((theChar == kDownCursor) && (commandKeyDown || (selLine == numLines - 1))) {
  951.             selChar = textLength;
  952.             isUpDownArrow = FALSE;
  953.         }
  954.         else {
  955.             if (!fUpDownArrow) {
  956.                 GetCharPoint(nonAnchor, &charPt);
  957.                 itsUpDownHOffset = charPt.h;
  958.             }
  959.             
  960.             if (optionKeyDown) {
  961.                 short    hSpan, vSpan;
  962.                 
  963.                 GetFrameSpan(&hSpan, &vSpan);
  964.                 if (theChar == kUpCursor)
  965.                     selLine -= vSpan;
  966.                 else
  967.                     selLine += vSpan;
  968.             }
  969.             else {
  970.                 if (theChar == kUpCursor)
  971.                     --selLine;
  972.                 else
  973.                     ++selLine;
  974.             }
  975.             
  976.             GetCharPoint(GetLineStart(selLine), &charPt);
  977.             charPt.h = (long)itsUpDownHOffset;
  978.             selChar = GetCharOffset(&charPt);
  979.             isUpDownArrow = TRUE;
  980.         }
  981.     }
  982.     
  983.     // Set the new selection, extending it if the Shift key was down
  984.     if (!shiftKeyDown)
  985.         SetSelection(selChar, selChar, kRedraw);
  986.     else {
  987.         newStart = Min(selChar, itsSelAnchor);
  988.         newEnd = Max(selChar, itsSelAnchor);
  989.         
  990.         HideCaret();
  991.         
  992.         if ((selStart == selEnd) || (newStart == newEnd))
  993.             SetSelection(newStart, newEnd, kRedraw);
  994.         else {
  995.             if (newStart != selStart)
  996.                 HiliteTextRange(Min(newStart, selStart), Max(newStart, selStart));
  997.             if (newEnd != selEnd)
  998.                 HiliteTextRange(Min(newEnd, selEnd), Max(newEnd, selEnd));
  999.             SetSelection(newStart, newEnd, kNoRedraw);
  1000.         }
  1001.     }
  1002.     
  1003.     // Make sure the selected character is visible within the frame
  1004.     ScrollToOffset(selChar);
  1005.     
  1006.     // Remember if this character was an up or down arrow
  1007.     fUpDownArrow = isUpDownArrow;
  1008. }
  1009.  
  1010.  
  1011. /**** T E X T   S P E C I F I C A T I O N   M E T H O D S ****/
  1012.  
  1013.  
  1014. /******************************************************************************
  1015.  SetTextPtr {OVERRIDE}
  1016.  
  1017.         Replace all the text with the text in the given buffer.
  1018.  ******************************************************************************/
  1019.  
  1020. void    CPEditText::SetTextPtr(Ptr textPtr, long numChars)
  1021. {
  1022.     long    prevSelStart = itsSelStart;
  1023.     long    prevSelEnd = itsSelEnd;
  1024.     
  1025.     // Check that enough memory is available for the operation to succeed
  1026.     TRY {
  1027.         SetSelection(0, itsTextLength, kNoRedraw);
  1028.         CheckInsertion(textPtr, numChars, kUseSelection, NULL, NULL);
  1029.     }
  1030.     CATCH {
  1031.         SetSelection(prevSelStart, prevSelEnd, kNoRedraw);
  1032.     }
  1033.     ENDTRY
  1034.     
  1035.     // Resize the text handle and copy the contents of the given buffer
  1036.     // into it, then recalculate the line starts and bounds rectangle
  1037. #if qPEUseInsertionGap
  1038.     CloseGap();
  1039. #endif
  1040.     SetHandleSize(itsTextHandle, numChars);
  1041.     FailMemError();
  1042.     BlockMove(textPtr, *itsTextHandle, numChars);
  1043.     itsTextLength = numChars;
  1044.     itsSelStart = itsSelEnd = 0;
  1045.     
  1046.     CalcLineStarts();
  1047.     Refresh();
  1048. }
  1049.  
  1050.  
  1051. /******************************************************************************
  1052.  UseTextHandle
  1053.  
  1054.         Replace all the text with the contents of a given handle.  Unlike
  1055.         SetTextHandle, this method does not copy the contents of the handle,
  1056.         so the caller must not dispose of the handle.
  1057.  ******************************************************************************/
  1058.  
  1059. void    CPEditText::UseTextHandle(Handle textHandle)
  1060. {
  1061.     ASSERT(textHandle != NULL);
  1062.     if (textHandle != itsTextHandle) {
  1063.         ForgetHandle(itsTextHandle);
  1064.         itsTextHandle = textHandle;
  1065.     }
  1066.     itsTextLength = GetHandleSize(textHandle);
  1067.     itsSelStart = itsSelEnd = 0;
  1068. #if qPEUseInsertionGap
  1069.     itsGapPosition = itsGapLength = 0;
  1070. #endif
  1071.         
  1072.     CalcLineStarts();
  1073.     Refresh();
  1074. }
  1075.  
  1076.  
  1077. /******************************************************************************
  1078.  InsertTextPtr {OVERRIDE}
  1079.  
  1080.         Insert a copy of the given text at the start of the selection.
  1081.  ******************************************************************************/
  1082.  
  1083. void    CPEditText::InsertTextPtr(Ptr insertPtr, long insertLen, Boolean redraw)
  1084. {
  1085.     long        numInsertCRs;
  1086.     
  1087.     // Check that enough memory is available for the insertion to succeed
  1088.     // As a side effect, CheckInsertion also counts the number of carriage
  1089.     // returns in the text being inserted
  1090.     CheckInsertion(insertPtr, insertLen, kDontUseSelection, &numInsertCRs, NULL);
  1091.         
  1092.     // Call the internal method InsertText to insert the text
  1093.     InsertText(insertPtr, insertLen, numInsertCRs, redraw);
  1094. }
  1095.  
  1096.  
  1097. /******************************************************************************
  1098.  CopyTextRange {OVERRIDE}
  1099.  
  1100.         Return a handle to a copy of the given range of text.
  1101.  ******************************************************************************/
  1102.  
  1103. Handle    CPEditText::CopyTextRange(long start, long end)
  1104. {
  1105.     Handle        copyHandle;
  1106.     long        length;
  1107.     
  1108.     // Determine the length of text to copy
  1109.     end = Min(end, itsTextLength);
  1110.     length = Max(end - start, 0);
  1111.     
  1112.     // Allocate a handle for the copied text
  1113.     copyHandle = NewHandleCanFail(length);
  1114.     FailNIL(copyHandle);
  1115.     
  1116.     // Copy the text from our buffer to the handle just allocated
  1117.     if (length > 0) {
  1118. #if qPEUseInsertionGap
  1119.         Ptr        textP = *itsTextHandle;
  1120.         long    gapPosition = itsGapPosition;
  1121.         long    gapLength = itsGapLength;
  1122.         
  1123.         if (end <= gapPosition)
  1124.             BlockMove(textP + start, *copyHandle, length);
  1125.         else if (start >= gapPosition)
  1126.             BlockMove(textP + start + gapLength, *copyHandle, length);
  1127.         else {
  1128.             long    lengthBeforeGap = gapPosition - start;
  1129.             BlockMove(textP + start, *copyHandle, lengthBeforeGap);
  1130.             BlockMove(textP + start + gapLength + lengthBeforeGap, *copyHandle + lengthBeforeGap, length - lengthBeforeGap);
  1131.         }
  1132. #else
  1133.         BlockMove(*itsTextHandle + start, *copyHandle, length);
  1134. #endif
  1135.     }
  1136.     
  1137.     return copyHandle;
  1138. }
  1139.  
  1140.  
  1141. /******************************************************************************
  1142.  DeleteTextRange
  1143.  
  1144.         Delete the given range of text.
  1145.  ******************************************************************************/
  1146.  
  1147. void    CPEditText::DeleteTextRange(long start, long end, Boolean redraw)
  1148. {
  1149.     DeleteText(start, end, CountRangeCRs(start, end), redraw);
  1150. }
  1151.  
  1152.  
  1153. /******************************************************************************
  1154.  ReplaceTextRange
  1155.  
  1156.         Replace a range of characters with the given text.
  1157.  ******************************************************************************/
  1158.  
  1159. void    CPEditText::ReplaceTextRange(long start, long end, Ptr replacePtr, long replaceLen)
  1160. {
  1161.     HideSelection(kHideSelection, kRedraw);
  1162.     SetSelection(start, end, kNoRedraw);
  1163.     ReplaceSelection(replacePtr, replaceLen);
  1164. }
  1165.  
  1166.  
  1167. /******************************************************************************
  1168.  ReplaceSelection
  1169.  
  1170.         Replace the current selection with the given text.  If replacePtr
  1171.         is NULL, just delete the current selection.
  1172.  ******************************************************************************/
  1173.  
  1174. void    CPEditText::ReplaceSelection(Ptr replacePtr, long replaceLen)
  1175. {
  1176.     Boolean        refresh;
  1177.     long        selStart;
  1178.     long        selEnd;
  1179.     long        numReplaceCRs;
  1180.     long        numInsertCRs;
  1181.     long        numLinesDelta;
  1182.     
  1183.     // Check that enough memory is available for the replacement to succeed
  1184.     // As a side effect, CheckInsertion also counts the number of carriage
  1185.     // returns in the text being inserted and in the current selection
  1186.     CheckInsertion(replacePtr, replaceLen, kUseSelection, &numInsertCRs, &numReplaceCRs);
  1187.     
  1188.     // Delete the current selection, if any
  1189.     GetSelection(&selStart, &selEnd);
  1190.     if (selStart < selEnd) {
  1191.         DeleteText(selStart, selEnd, numReplaceCRs, kNoRedraw);
  1192.         numLinesDelta = -numReplaceCRs;
  1193.         refresh = TRUE;
  1194.     }
  1195.     else {
  1196.         numLinesDelta = 0;
  1197.         refresh = FALSE;
  1198.     }
  1199.     
  1200.     // Insert the new text, if any
  1201.     if ((replacePtr != NULL) && (replaceLen > 0)) {
  1202.         InsertText(replacePtr, replaceLen, numInsertCRs, kNoRedraw);
  1203.         SetSelection(selStart + replaceLen, selStart + replaceLen, kNoRedraw);
  1204.         refresh = (numLinesDelta != 0);
  1205.     }
  1206.     
  1207.     // Refresh the text if necessary
  1208.     if (refresh)
  1209.         RefreshTextAfter(selStart, (numLinesDelta == 0));
  1210. }
  1211.  
  1212.  
  1213. /******************************************************************************
  1214.  PerformEditCommand {OVERRIDE}
  1215.  
  1216.         Perform the standard cut, copy, paste, and clear commands on the text.
  1217.  ******************************************************************************/
  1218.  
  1219. void    CPEditText::PerformEditCommand(long theCommand)
  1220. {
  1221.     Handle        textH;
  1222.     long        selStart;
  1223.     long        selEnd;
  1224.     
  1225.     Prepare();
  1226.     GetSelection(&selStart, &selEnd);
  1227.     
  1228.     // Copy the selection range to the Clipboard for Cut or Copy
  1229.     if (((theCommand == cmdCut) || (theCommand == cmdCopy)) && (selStart != selEnd)) {
  1230.         textH = CopyTextRange(selStart, selEnd);
  1231.         TRY {
  1232.             gClipboard->EmptyScrap();
  1233.             gClipboard->PutData('TEXT', textH);
  1234.             DisposHandle(textH);
  1235.         }
  1236.         CATCH {
  1237.             ForgetHandle(textH);
  1238.         }
  1239.         ENDTRY
  1240.     }
  1241.     
  1242.     // Delete the selection range for Cut or Clear
  1243.     if (((theCommand == cmdCut) || (theCommand == cmdClear)) && (selStart < selEnd))
  1244.         DeleteTextRange(selStart, selEnd, kRedraw);
  1245.         
  1246.     // Replace the current selection with the contents of the Clipboard for Paste
  1247.     else if ((theCommand == cmdPaste) && gClipboard->GetData('TEXT', &textH)) {
  1248.         HLockHi(textH);
  1249.         TRY {
  1250.             ReplaceSelection(*textH, GetHandleSize(textH));
  1251.             DisposHandle(textH);
  1252.         }
  1253.         CATCH {
  1254.             ForgetHandle(textH);
  1255.         }
  1256.         ENDTRY
  1257.     }
  1258.     
  1259.     // Ensure that the insertion caret is visible
  1260.     ScrollToOffset(itsSelStart);
  1261. }
  1262.  
  1263.  
  1264. /**** T E X T   C H A R A C T E R I S T I C S   M E T H O D S ****/
  1265.  
  1266.  
  1267. /******************************************************************************
  1268.  SetFontNumber {OVERRIDE}
  1269.  
  1270.         Specify the font for text by font number.
  1271.  ******************************************************************************/
  1272.  
  1273. void    CPEditText::SetFontNumber(short aFontNumber)
  1274. {
  1275.     TextInfoRec        textInfo;
  1276.     
  1277.     itsTextFont = aFontNumber;
  1278.     
  1279.     ((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
  1280.     textInfo.fontNumber = aFontNumber;
  1281.     ((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
  1282.     
  1283.     CalcLineHeight();
  1284. }
  1285.     
  1286.  
  1287. /******************************************************************************
  1288.  SetFontSize {OVERRIDE}
  1289.  
  1290.         Specify the point size of text.
  1291.  ******************************************************************************/
  1292.  
  1293. void    CPEditText::SetFontSize(short aSize)
  1294. {
  1295.     TextInfoRec        textInfo;
  1296.     
  1297.     itsTextSize = aSize;
  1298.  
  1299.     ((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
  1300.     textInfo.theSize = aSize;
  1301.     ((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
  1302.     
  1303.     CalcLineHeight();
  1304. }
  1305.     
  1306.  
  1307. /******************************************************************************
  1308.  SetFontStyle {OVERRIDE}
  1309.  
  1310.         Specify the style, such as bold or italic, for text.
  1311.  ******************************************************************************/
  1312.  
  1313. void    CPEditText::SetFontStyle(short aStyle)
  1314. {
  1315.     TextInfoRec        textInfo;
  1316.     
  1317.     if (aStyle == normal)            // Plain text is the absence of any style
  1318.         itsTextFace = normal;
  1319.     else
  1320.         itsTextFace ^= aStyle;        // Toggle style characteristic by XOR'ing proper bit
  1321.     
  1322.     ((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
  1323.     textInfo.theStyle = itsTextFace;
  1324.     ((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
  1325.     
  1326.     CalcLineHeight();
  1327. }
  1328.     
  1329.  
  1330. /******************************************************************************
  1331.  SetTextMode {OVERRIDE}
  1332.  
  1333.         Specify the transfer mode used for drawing text.
  1334.  ******************************************************************************/
  1335.  
  1336. void    CPEditText::SetTextMode(short aMode)
  1337. {
  1338.     TextInfoRec        textInfo;
  1339.     
  1340.     itsTextMode = aMode;
  1341.     
  1342.     ((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
  1343.     textInfo.theMode = aMode;
  1344.     ((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
  1345.     
  1346.     Refresh();
  1347. }
  1348.     
  1349.  
  1350. /******************************************************************************
  1351.  SetAlignCmd {OVERRIDE}
  1352.  
  1353.         Specify the alignment for text.
  1354.  ******************************************************************************/
  1355.  
  1356. void    CPEditText::SetAlignCmd(long anAlignCmd)
  1357. {
  1358.     // Null method -- alignment not supported by CPEditText
  1359. }
  1360.  
  1361.     
  1362. /******************************************************************************
  1363.  GetAlignCmd {OVERRIDE}
  1364.  
  1365.         Return the current alignment.
  1366.  ******************************************************************************/
  1367.  
  1368. long    CPEditText::GetAlignCmd()
  1369. {
  1370.     return cmdAlignLeft;
  1371. }
  1372.  
  1373.  
  1374. /******************************************************************************
  1375.  SetSpacingCmd {OVERRIDE}
  1376.  
  1377.         Specify the vertical spacing between lines of text.
  1378.  ******************************************************************************/
  1379.  
  1380. void    CPEditText::SetSpacingCmd(long aSpacingCmd)
  1381. {
  1382.     itsSpacingCmd = aSpacingCmd;
  1383.     CalcLineHeight();
  1384. }
  1385.  
  1386.  
  1387. /******************************************************************************
  1388.  GetSpacingCmd {OVERRIDE}
  1389.  
  1390.         Return the vertical spacing between lines of text.
  1391.  ******************************************************************************/
  1392.  
  1393. long    CPEditText::GetSpacingCmd()
  1394. {
  1395.     return itsSpacingCmd;
  1396. }
  1397.  
  1398. #if !qTCL113
  1399.  
  1400. /******************************************************************************
  1401.  SetHorizontalScroll
  1402.  
  1403.         Enable/disable automatic horizontal scrolling. If TRUE, typing
  1404.         and editing can cause automatic horizontal scrolling to keep the
  1405.         selection range or insertion point in view. (Automatic vertical
  1406.         scrolling is always enabled.) If FALSE, the insertion point is
  1407.         allowed to travel out of sight.
  1408.         
  1409.         (This method is provided in CAbstractText with TCL 1.1.3, so
  1410.         it is only necessary when compiling for TCL 1.1.2 or earlier.)
  1411.  ******************************************************************************/
  1412.         
  1413. void    CPEditText::SetHorizontalScroll(Boolean doHoriz)
  1414. {
  1415.     scrollHoriz = doHoriz;
  1416. }
  1417.  
  1418. #endif    // !qTCL113
  1419.  
  1420. /******************************************************************************
  1421.  SetTabSpaces
  1422.  
  1423.         Set the number of spaces per tab.
  1424.  ******************************************************************************/
  1425.  
  1426. void    CPEditText::SetTabSpaces(short tabSpaces)
  1427. {
  1428.     itsTabSpaces = Max(tabSpaces, 1);
  1429.     CalcTabWidth();
  1430.     Refresh();
  1431. }
  1432.  
  1433.  
  1434. /******************************************************************************
  1435.  GetTabSpaces
  1436.  
  1437.         Return the number of spaces per tab.
  1438.  ******************************************************************************/
  1439.  
  1440. short    CPEditText::GetTabSpaces()
  1441. {
  1442.     return itsTabSpaces;
  1443. }
  1444.  
  1445.  
  1446. /******************************************************************************
  1447.  SetOutlineHiliting
  1448.  
  1449.         Set whether or not to outline an inactive selection range and
  1450.         to draw the inactive insertion caret in gray.
  1451.  ******************************************************************************/
  1452.  
  1453. void    CPEditText::SetOutlineHiliting(Boolean outlineHilite)
  1454. {
  1455.     fOutlineHilite = outlineHilite;
  1456.     if (!fReallyActive)
  1457.         Refresh();
  1458. }
  1459.  
  1460.  
  1461. /******************************************************************************
  1462.  SetItalicCaret
  1463.  
  1464.         Set whether or not to use a slanted cursor for italic text.
  1465.  ******************************************************************************/
  1466.  
  1467. void    CPEditText::SetItalicCaret(Boolean useItalicCaret)
  1468. {
  1469.     HideSelection(kHideSelection, kRedraw);
  1470.     fUseItalicCaret = useItalicCaret;
  1471.     HideSelection(kUnhideSelection, kRedraw);
  1472. }
  1473.  
  1474.  
  1475. /******************************************************************************
  1476.  SetShowInvisibles
  1477.  
  1478.          Set whether or not to show invisible characters.
  1479.  ******************************************************************************/
  1480.  
  1481. void    CPEditText::SetShowInvisibles(Boolean showInvisibles)
  1482. {
  1483.     fShowInvisibles = showInvisibles;
  1484.     Refresh();
  1485. }
  1486.  
  1487.  
  1488. /******************************************************************************
  1489.  GetShowInvisibles
  1490.  
  1491.          Return whether or not invisible characters are shown.
  1492.  ******************************************************************************/
  1493.  
  1494. Boolean    CPEditText::GetShowInvisibles()
  1495. {
  1496.     return fShowInvisibles;
  1497. }
  1498.  
  1499.  
  1500. /******************************************************************************
  1501.  GetHeight {OVERRIDE}
  1502.  
  1503.         Return the height of the indicated lines of text.
  1504.  ******************************************************************************/
  1505.  
  1506. long    CPEditText::GetHeight(long startLine, long endLine)
  1507. {
  1508.     return (itsLineHeight * (endLine - startLine + 1));
  1509. }
  1510.  
  1511.  
  1512. /******************************************************************************
  1513.  GetCharOffset {OVERRIDE}
  1514.  
  1515.         Return the offset into the text buffer of the character position
  1516.         at a point in Frame coordinates.  The offset does not reflect
  1517.         the position or length of the insertion gap.
  1518.  ******************************************************************************/
  1519.  
  1520. long    CPEditText::GetCharOffset(LongPt *aPt)
  1521. {
  1522.     long            line;
  1523.     register short    offset;
  1524.     register short    lineLen;
  1525.     register short    width;
  1526.     register short    lastWidth;
  1527.     register short    horiz;
  1528.     register short    *widthsP;
  1529.     ShortHandle        widthsH;
  1530.     
  1531.     Prepare();
  1532.     
  1533.     // Determine which line the point lies on
  1534.     line = (aPt->v - VertInset()) / itsLineHeight;
  1535.     
  1536.     // Check for and handle special cases above the first line and below the last line
  1537.     if (line < 0)
  1538.         return 0;
  1539.     else if (line > itsNumLines - 1)
  1540.         return itsTextLength;
  1541.     
  1542.     // Get the number of characters in the line
  1543.     lineLen = GetLineLength(line);
  1544.     
  1545.     // Check if the horizontal coordinate is smaller than the horizontal inset of the pane
  1546.     horiz = aPt->h - HorizInset();
  1547.     
  1548.     if (horiz <= 0)
  1549.         offset = 0;
  1550.     else {
  1551.         
  1552.         // Calculate character pixel offsets for the given line
  1553.         widthsH = MeasureLineWidths(line);
  1554.         widthsP = *widthsH;
  1555.         
  1556.         offset = 0;
  1557.         lastWidth = 0;
  1558.         
  1559.         while ((offset < lineLen) && ((width = *++widthsP) < horiz)) {
  1560.             lastWidth = width;
  1561.             ++offset;
  1562.         }
  1563.         
  1564.         DisposHandle((Handle)widthsH);
  1565.         
  1566.         // Bump the offset if the point is on the right side of a character
  1567.         if (horiz >= (width + lastWidth) / 2)
  1568.             ++offset;
  1569.         
  1570.         // Set the offset to the last character on the line if the point
  1571.         // is greater than the pixel length of the line
  1572.         if (offset > lineLen || ((offset == lineLen) && (GetChar(offset) == kReturn)))
  1573.             offset = (line < itsNumLines - 1 ? lineLen - 1 : lineLen);
  1574.     }
  1575.     
  1576.     return ((*itsLineStarts)[line] + offset);
  1577. }
  1578.  
  1579.  
  1580. /******************************************************************************
  1581.  GetCharPoint {OVERRIDE}
  1582.  
  1583.          Return the Frame coordinates of the character at the given offset
  1584.          in the text buffer.  The offset should not take into account the
  1585.          position or length of the insertion gap.
  1586.  ******************************************************************************/
  1587.  
  1588. void    CPEditText::GetCharPoint(long charOffset, LongPt *aPt)
  1589. {
  1590.     long        line;
  1591.     ShortHandle    widthsH;
  1592.     
  1593.     // Ensure that the offset is within bounds
  1594.     charOffset = Max(charOffset, 0);
  1595.     charOffset = Min(charOffset, itsTextLength);
  1596.     
  1597.     // Determine which line the given character offset is on
  1598.     // and calculate the pixel widths for that line
  1599.     line = FindLine(charOffset);
  1600.     widthsH = MeasureLineWidths(line);
  1601.     
  1602.     // Compute the pixel coordinates for the character
  1603.     aPt->v = VertInset() + line * itsLineHeight + itsFontAscent;
  1604.     aPt->h = HorizInset() + (*widthsH)[charOffset - (*itsLineStarts)[line]];
  1605.     
  1606.     // Dispose of the pixel widths handle
  1607.     DisposHandle((Handle)widthsH);
  1608. }
  1609.  
  1610.  
  1611. /******************************************************************************
  1612.  GetTextStyle {OVERRIDE}
  1613.  
  1614.         Return current style information. whichAttributes is a set of
  1615.         flags indicating which text attributes are requested, and upon
  1616.         return, indicates which of the requested attributes are valid
  1617.         in the TextStyle record.
  1618. ******************************************************************************/
  1619.  
  1620. void    CPEditText::GetTextStyle(short *whichAttributes, TextStyle *aStyle)
  1621. {
  1622.     if (*whichAttributes & doFont)
  1623.         aStyle->tsFont = itsTextFont;
  1624.     if (*whichAttributes & doFace)
  1625.         aStyle->tsFace = itsTextFace;
  1626.     if (*whichAttributes & doSize)
  1627.         aStyle->tsSize = itsTextSize;
  1628.     *whichAttributes &= ~doColor;
  1629. }
  1630.  
  1631.  
  1632. /******************************************************************************
  1633.  GetCharStyle {OVERRIDE}
  1634.  
  1635.         Return style information about the character at the given offset.
  1636.         Since a PEditText pane only supports a single font/size/style
  1637.         for the text, this method returns information about the global
  1638.         text characteristics in the TextStyle record.
  1639. ******************************************************************************/
  1640.  
  1641. void    CPEditText::GetCharStyle(long charOffset, TextStyle *aStyle)
  1642. {
  1643.     aStyle->tsFont = itsTextFont;
  1644.     aStyle->tsFace = itsTextFace;
  1645.     aStyle->tsSize = itsTextSize;
  1646.     aStyle->tsColor.red = aStyle->tsColor.green = aStyle->tsColor.blue = 0;
  1647. }
  1648.  
  1649.  
  1650. /**** A C C E S S I N G   M E T H O D S ****/
  1651.  
  1652.  
  1653. /******************************************************************************
  1654.  SetBounds {OVERRIDE}
  1655.  
  1656.         Set the bounds of the PEditText panorama.  The text is automatically
  1657.         scrolled if necessary to fill the entire frame.
  1658.  ******************************************************************************/
  1659.  
  1660. void    CPEditText::SetBounds(LongRect *aBounds)
  1661. {
  1662.     long        hDelta;
  1663.     long        vDelta;
  1664.     
  1665.     // Scroll the text if necessary to fill the entire frame
  1666.     if (aBounds->right * hScale < frame.right)
  1667.         hDelta = Min(frame.left - aBounds->left * hScale, frame.right - aBounds->right * hScale);
  1668.     else
  1669.         hDelta = 0;
  1670.     
  1671.     if (aBounds->bottom * vScale < frame.bottom)
  1672.         vDelta = Min(frame.top - aBounds->top * vScale, frame.bottom - aBounds->bottom * vScale);
  1673.     else
  1674.         vDelta = 0;
  1675.     
  1676.     if ((hDelta != 0) || (vDelta != 0))
  1677.         Scroll(-hDelta / hScale, -vDelta / vScale, kRedraw);
  1678.     
  1679.     // Call the inherited method to update the bounds instance variable
  1680.     // and adjust the maximum values of the scroll bars
  1681.     inherited::SetBounds(aBounds);
  1682. }
  1683.  
  1684.  
  1685. #if qTCL113
  1686.  
  1687. /******************************************************************************
  1688.  GetSteps {OVERRIDE}
  1689.  
  1690.         Return the horizontal and vertical number of units to scroll
  1691.         in a single step.
  1692.  ******************************************************************************/
  1693.         
  1694. void    CPEditText::GetSteps(short *hStep, short *vStep)
  1695. {
  1696.     inherited::GetSteps(hStep, vStep);
  1697.  
  1698.     if ((*hStep == 1) && (hScale == 1))
  1699.         *hStep = itsMaxCharWidth;
  1700. }
  1701.  
  1702. #endif    // qTCL113
  1703.  
  1704. /******************************************************************************
  1705.  GetTextHandle {OVERRIDE}
  1706.  
  1707.         Return a handle to the text buffer.  This method closes the
  1708.         insertion gap before returning the text handle, which can have
  1709.         a negative impact on performance.  Clients who frequently call
  1710.         this method should considering calling GetRawTextHandle instead,
  1711.         although doing so will require taking into account the position
  1712.         and length of the gap.
  1713.  ******************************************************************************/
  1714.  
  1715. Handle    CPEditText::GetTextHandle()
  1716. {
  1717. #if qPEUseInsertionGap
  1718.     CloseGap();
  1719. #endif
  1720.     return itsTextHandle;
  1721. }
  1722.  
  1723. #if qPEUseInsertionGap
  1724.  
  1725. /******************************************************************************
  1726.  GetRawTextHandle
  1727.  
  1728.         Return a handle to the text buffer.  Clients should generally
  1729.         use GetTextHandle instead of GetRawTextHandle, unless they are
  1730.         willing to contend with the insertion gap.
  1731.  ******************************************************************************/
  1732.  
  1733. Handle    CPEditText::GetRawTextHandle()
  1734. {
  1735.     return itsTextHandle;
  1736. }
  1737.  
  1738. #endif // qPEUseInsertionGap
  1739.  
  1740. /******************************************************************************
  1741.  FindLine {OVERRIDE}
  1742.  
  1743.         Return the line number containing the specified character position.
  1744.         Both lines and character positions are numbered starting from zero.
  1745.         If the character position is before the start of the text (i.e., a
  1746.         negative number), a value of zero is returned; if it is beyond the
  1747.         end of the text, the number of the last line is returned.
  1748.  ******************************************************************************/
  1749.  
  1750. long    CPEditText::FindLine(register long charPos)
  1751. {
  1752.     register long    line, lineLow, lineHigh;
  1753.     register long    lineStart;
  1754.     register long    *lineStarts = *itsLineStarts;
  1755.     long            numLines = itsNumLines;
  1756.     
  1757.     // Check if it's in the first line
  1758.     if ((numLines == 1) || (charPos < lineStarts[1]))
  1759.         return 0;
  1760.     
  1761.     // Check if it's in the last line
  1762.     if (charPos >= lineStarts[numLines - 1])
  1763.         return numLines - 1;
  1764.     
  1765.     // Perform a binary search through itsLineStarts        
  1766.     lineLow = 0;
  1767.     lineHigh = numLines;
  1768.     
  1769.     while (lineHigh >= lineLow) {
  1770.         line = (lineLow + lineHigh) >> 1;
  1771.         lineStart = lineStarts[line];
  1772.         if (charPos == lineStart)
  1773.             break;
  1774.         else if (charPos > lineStart)
  1775.             lineLow = line + 1;
  1776.         else
  1777.             lineHigh = line - 1;
  1778.     }
  1779.     
  1780.     return ((charPos < lineStart) ? line - 1 : line);
  1781. }
  1782.  
  1783.  
  1784. /******************************************************************************
  1785.  GetLineStart
  1786.  
  1787.          Return the offset of the character at the start of the given line. 
  1788.          The line number parameter is zero-based.  The offset does not
  1789.          reflect the position or length of the insertion gap.
  1790.  ******************************************************************************/
  1791.  
  1792. long    CPEditText::GetLineStart(long line)
  1793. {
  1794.     if (line <= 0)
  1795.         return 0;
  1796.     else if (line >= itsNumLines)
  1797.         return itsTextLength;
  1798.     else
  1799.         return (*itsLineStarts)[line];
  1800. }
  1801.  
  1802.  
  1803. /******************************************************************************
  1804.  GetLineEnd
  1805.  
  1806.          Return the offset of the character at the end of the given line.
  1807.          The line number parameter is zero-based.  The offset does not
  1808.          reflect the position or length of the insertion gap.
  1809.  ******************************************************************************/
  1810.  
  1811. long    CPEditText::GetLineEnd(long line)
  1812. {
  1813.     if (line < 0)
  1814.         return 0;
  1815.     else if (line >= itsNumLines - 1)
  1816.         return itsTextLength;
  1817.     else
  1818.         return (*itsLineStarts)[line + 1];
  1819. }
  1820.  
  1821.  
  1822. /******************************************************************************
  1823.  GetLineLength
  1824.  
  1825.         Return the number of characters in the given line.  The length does
  1826.          not reflect the length of the insertion gap.
  1827.  ******************************************************************************/
  1828.  
  1829. short    CPEditText::GetLineLength(long line)
  1830. {
  1831.     long    lineStart;
  1832.     long    lineEnd;
  1833.     short    lineLength;
  1834.     
  1835.     if ((line < 0) || (line >= itsNumLines))
  1836.         lineLength = 0;
  1837.     else {
  1838.         lineStart = (*itsLineStarts)[line];
  1839.         lineEnd = (line < itsNumLines - 1 ? (*itsLineStarts)[line + 1] : itsTextLength);
  1840.         lineLength = lineEnd - lineStart;
  1841.     }
  1842.         
  1843.     return lineLength;
  1844. }
  1845.  
  1846.  
  1847. /******************************************************************************
  1848.  GetLength {OVERRIDE}
  1849.  
  1850.          Return the number of characters in the text buffer.  The length does
  1851.          not reflect the length of the insertion gap.
  1852.  ******************************************************************************/
  1853.  
  1854. long    CPEditText::GetLength()
  1855. {
  1856.     return itsTextLength;
  1857. }
  1858.  
  1859.  
  1860. /******************************************************************************
  1861.  GetNumLines {OVERRIDE}
  1862.  
  1863.         Return the number of lines of text.
  1864.  ******************************************************************************/
  1865.  
  1866. long    CPEditText::GetNumLines()
  1867. {
  1868.     return itsNumLines;
  1869. }
  1870.  
  1871.  
  1872. /******************************************************************************
  1873.  GetSelection {OVERRIDE}
  1874.  
  1875.         Return the start and end of the selection.  The positions do not
  1876.         reflect the position or length of the insertion gap.
  1877.  ******************************************************************************/
  1878.  
  1879. void    CPEditText::GetSelection(long *selStart, long *selEnd)
  1880. {
  1881.     *selStart = itsSelStart;
  1882.     *selEnd = itsSelEnd;
  1883. }
  1884.  
  1885.  
  1886. /******************************************************************************
  1887.  GetChar
  1888.  
  1889.         Return the character at the given position.
  1890.  ******************************************************************************/
  1891.  
  1892. short    CPEditText::GetChar(long aPosition)
  1893. {
  1894. #if qPEUseInsertionGap
  1895.     Ptr        textP = *itsTextHandle;
  1896.     
  1897.     if (aPosition >= itsGapPosition)
  1898.         textP += itsGapLength;
  1899.     
  1900.     return (unsigned char)textP[aPosition];
  1901. #else
  1902.     return (unsigned char)(*itsTextHandle)[aPosition];
  1903. #endif
  1904. }
  1905.  
  1906.  
  1907. /******************************************************************************
  1908.  GetCharBefore {OVERRIDE}
  1909.  
  1910.         Return the character before aPosition. The character and its size
  1911.         are returned in charBuf, and aPosition is updated with the starting
  1912.         position of the character. If there is no preceding character, then
  1913.         Length(charBuf) is 0, and aPosition is not updated.
  1914.  ******************************************************************************/
  1915.  
  1916. void    CPEditText::GetCharBefore(long *aPosition, tCharBuf charBuf)
  1917. {
  1918.     long    charPos = *aPosition;
  1919.     
  1920.     if ((charPos > 0) && (charPos <= itsTextLength)) {
  1921.         charPos--;
  1922.         Length(charBuf) = 1;
  1923. #if qPEUseInsertionGap
  1924.         charBuf[1] = (*itsTextHandle)[charPos < itsGapPosition ? charPos : charPos + itsGapLength];
  1925. #else
  1926.         charBuf[1] = (*itsTextHandle)[charPos];
  1927. #endif
  1928.         *aPosition = charPos;
  1929.     }
  1930.     else
  1931.         Length(charBuf) = 0;
  1932. }
  1933.  
  1934.  
  1935. /******************************************************************************
  1936.  GetCharAfter {OVERRIDE}
  1937.  
  1938.         Return the character after aPosition. The character and its size
  1939.         are returned in charBuf, and aPosition is updated with the starting
  1940.         position of the character. If there is no following character, then
  1941.         Length(charBuf) is 0, and aPosition is not updated.
  1942.  ******************************************************************************/
  1943.  
  1944. void    CPEditText::GetCharAfter(long *aPosition, tCharBuf charBuf)
  1945. {
  1946.     long    charPos = *aPosition;
  1947.     
  1948.     if ((charPos >= 0) && (charPos < itsTextLength)) {
  1949.         Length(charBuf) = 1;
  1950. #if qPEUseInsertionGap
  1951.         charBuf[1] = (*itsTextHandle)[charPos < itsGapPosition ? charPos : charPos + itsGapLength];
  1952. #else
  1953.         charBuf[1] = (*itsTextHandle)[charPos];
  1954. #endif
  1955.     }
  1956.     else
  1957.         Length(charBuf) = 0;
  1958. }
  1959.  
  1960.  
  1961. /******************************************************************************
  1962.  GetWordBounds
  1963.  
  1964.         Find the starting and ending offsets of the word containing
  1965.         the given character offset.  Returns TRUE if this character is part
  1966.         of a word, FALSE otherwise.
  1967.  ******************************************************************************/
  1968.  
  1969. Boolean    CPEditText::GetWordBounds(long charPos, long *wordStart, long *wordEnd)
  1970. {
  1971.     ASSERT(wordStart != NULL);
  1972.     ASSERT(wordEnd != NULL);
  1973.     
  1974.     if ((charPos < 0) || (charPos >= itsTextLength))
  1975.         return FALSE;
  1976.     else {
  1977.         *wordStart = WordBreakHook(charPos, kBreakLeft);
  1978.         *wordEnd = WordBreakHook(charPos, kBreakRight);
  1979.         return (*wordStart < *wordEnd);
  1980.     }
  1981. }
  1982.  
  1983.  
  1984. /******************************************************************************
  1985.  GetParagraphBounds
  1986.  
  1987.         Find the starting and ending offsets of the paragraph containing
  1988.         the given character offset.  Paragraphs are defined as a sequence
  1989.         of characters delineated by carriage returns or the beginning/end
  1990.         of the text.
  1991.  ******************************************************************************/
  1992.  
  1993. void    CPEditText::GetParagraphBounds(long charPos, long *paraStart, long *paraEnd)
  1994. {    
  1995.     if (paraStart != NULL) {
  1996.         register long    start = charPos;
  1997.         while (--start >= 0 && GetChar(start) != kReturn)
  1998.             ;
  1999.         *paraStart = ++start;
  2000.     }
  2001.     
  2002.     if (paraEnd != NULL) {
  2003.         register long    end = charPos;
  2004.         while (end < itsTextLength && GetChar(end++) != kReturn)
  2005.             ;
  2006.         *paraEnd = end;
  2007.     }
  2008. }
  2009.  
  2010.  
  2011. #if qPEUseInsertionGap
  2012.  
  2013. /******************************************************************************
  2014.  GetGapPosition
  2015.  
  2016.         Return the position of the insertion gap.  Clients will generally
  2017.         not need to use this method, as they should not need to know about
  2018.         the position or length of the gap.
  2019.  ******************************************************************************/
  2020.  
  2021. long    CPEditText::GetGapPosition()
  2022. {
  2023.     return itsGapPosition;
  2024. }
  2025.  
  2026.  
  2027. /******************************************************************************
  2028.  GetGapLength
  2029.  
  2030.         Return the length of the insertion gap.  Clients will generally
  2031.         not need to use this method, as they should not need to know about
  2032.         the position or length of the gap.
  2033.  ******************************************************************************/
  2034.  
  2035. long    CPEditText::GetGapLength()
  2036. {
  2037.     return itsGapLength;
  2038. }
  2039.  
  2040. #endif // qPEUseInsertionGap
  2041.  
  2042.  
  2043. /**** H O O K   M E T H O D S ****/
  2044.  
  2045.  
  2046. /******************************************************************************
  2047.  WordBreakHook
  2048.     
  2049.         Return the character offset at which the next word break in the
  2050.         specified direction should occur.  Override if a different work
  2051.         break criterion is desired.
  2052.  ******************************************************************************/
  2053.  
  2054. #define        TestBit(p,b)    (((long *)(p))[(b) >> 5] & (0x80000000U >> ((b) & 0x1F)))
  2055.  
  2056. long    CPEditText::WordBreakHook(register long charPos, BreakDirection direction)
  2057. {
  2058.     register Byte    c;
  2059.     register long    textLength;
  2060.     register Ptr    textP = *itsTextHandle;
  2061. #if qPEUseInsertionGap
  2062.     register long    gapLength = itsGapLength;
  2063.     long            gapPosition = itsGapPosition;
  2064.     register Ptr    gapP = textP + gapPosition;
  2065. #endif
  2066.     static long        wordBreaks[8] = {        // Packed boolean flags to indicate
  2067.         0x00000000, 0x0000FFC0,                // whether or not a character is a
  2068.         0x7FFFFFE1, 0x7FFFFFE0,                // word break character
  2069.         0x00000000, 0x00000000,
  2070.         0x00000000, 0x00000000
  2071.     };
  2072.     
  2073.     textP += charPos;
  2074. #if qPEUseInsertionGap
  2075.     if (charPos >= gapPosition)
  2076.         textP += gapLength;
  2077. #endif
  2078.         
  2079.     if (direction == kBreakLeft) {
  2080. #if qPEUseInsertionGap
  2081.         gapP += gapLength;
  2082. #endif
  2083.                 
  2084.         do {
  2085. #if qPEUseInsertionGap
  2086.             if (textP == gapP)
  2087.                 textP -= gapLength;
  2088. #endif
  2089.         } while (charPos-- && (c = *--textP, TestBit(wordBreaks, c)));
  2090.                 
  2091.         return charPos + 1;
  2092.     }
  2093.     else {
  2094.         textLength = itsTextLength;
  2095.         
  2096.         do {
  2097. #if qPEUseInsertionGap
  2098.             if (textP == gapP)
  2099.                 textP += gapLength;
  2100. #endif
  2101.         } while (charPos < textLength && (c = *textP++, TestBit(wordBreaks, c)) && ++charPos);
  2102.         
  2103.         return charPos;
  2104.     }
  2105. }
  2106.  
  2107.  
  2108. /******************************************************************************
  2109.  CaretHook
  2110.     
  2111.         Draw the text insertion caret.  The rectangle is in QuickDraw
  2112.         coordinates.  Override if a different caret appearance is desired.
  2113.  ******************************************************************************/
  2114.  
  2115. void    CPEditText::CaretHook(const Rect *caretRect)
  2116. {
  2117.     PenState    prevPenState;
  2118.     Boolean        canDrawInRealGray = FALSE;
  2119.     RGBColor    grayColor;
  2120.     RGBColor    prevForeColor;
  2121.     
  2122.     GetPenState(&prevPenState);
  2123.     PenMode(patXor);
  2124.     
  2125.     // Draw the caret in gray if the pane isn't active
  2126.     if (!fReallyActive && fOutlineHilite) {
  2127.         canDrawInRealGray = GetGrayRGBColor(caretRect, &grayColor, &prevForeColor);
  2128.         if (canDrawInRealGray)
  2129.             RGBForeColor(&grayColor);
  2130.         else
  2131.             PenPat(gray);
  2132.     }
  2133.     
  2134.     // Draw the caret
  2135.     if ((itsTextFace & italic) && fUseItalicCaret) {
  2136.         PenSize(1, 1);
  2137.         MoveTo(caretRect->left, caretRect->bottom);
  2138.         LineTo(caretRect->left + (caretRect->bottom - caretRect->top) / 2, caretRect->top);
  2139.     }
  2140.     else
  2141.         PaintRect(caretRect);
  2142.     
  2143.     SetPenState(&prevPenState);
  2144.     if (canDrawInRealGray)
  2145.         RGBForeColor(&prevForeColor);
  2146. }
  2147.  
  2148.  
  2149. /******************************************************************************
  2150.  HiliteHook
  2151.     
  2152.         Hilite the specified rectangle.  The rectangle is in QuickDraw
  2153.         coordinates.  Override if a different hiliting behavior is desired.
  2154.  ******************************************************************************/
  2155.  
  2156. void    CPEditText::HiliteHook(const Rect *hiliteRect)
  2157. {
  2158.     if ((itsTextFace & italic) && fUseItalicCaret) {
  2159.         OpenRgn();
  2160.         MoveTo(hiliteRect->left, hiliteRect->bottom);
  2161.         LineTo(hiliteRect->right, hiliteRect->bottom);
  2162.         LineTo(hiliteRect->right + (hiliteRect->bottom - hiliteRect->top) / 2, hiliteRect->top);
  2163.         LineTo(hiliteRect->left + (hiliteRect->bottom - hiliteRect->top) / 2, hiliteRect->top);
  2164.         LineTo(hiliteRect->left, hiliteRect->bottom);
  2165.         CloseRgn(gUtilRgn);
  2166.         SetHiliteMode();
  2167.         InvertRgn(gUtilRgn);
  2168.     }
  2169.     else {
  2170.         SetHiliteMode();
  2171.         InvertRect(hiliteRect);
  2172.     }
  2173. }
  2174.  
  2175.  
  2176. /**** I N T E R N A L   M E T H O D S ****/
  2177.  
  2178.  
  2179. #if qPEUseInsertionGap
  2180.  
  2181. /******************************************************************************
  2182.  SetGapPosition
  2183.  
  2184.         Internal method to set the position of the insertion gap.
  2185.  ******************************************************************************/
  2186.  
  2187. void    CPEditText::SetGapPosition(long newGapPosition)
  2188. {
  2189.     long    gapPosition = itsGapPosition;
  2190.     
  2191.     if ((newGapPosition != gapPosition) && (newGapPosition >= 0) && (newGapPosition <= itsTextLength)) {
  2192.         Ptr        textP = *itsTextHandle;
  2193.         long    textLength = itsTextLength;
  2194.         long    gapLength = itsGapLength;
  2195.         
  2196.         if (gapLength > 0) {
  2197.             if (newGapPosition > gapPosition)
  2198.                 BlockMove(textP + gapPosition + gapLength, textP + gapPosition, newGapPosition - gapPosition);
  2199.             else
  2200.                 BlockMove(textP + newGapPosition, textP + newGapPosition + gapLength, gapPosition - newGapPosition);
  2201.         }
  2202.         
  2203.         itsGapPosition = newGapPosition;
  2204.     }
  2205. }
  2206.  
  2207.  
  2208. /******************************************************************************
  2209.  SetGapLength
  2210.  
  2211.         Internal method to set the length of the insertion gap.
  2212.  ******************************************************************************/
  2213.  
  2214. void    CPEditText::SetGapLength(long newGapLength)
  2215. {
  2216.     if ((newGapLength != itsGapLength) && (newGapLength >= 0)) {
  2217.         long    gapLength = itsGapLength;
  2218.         long    gapPosition = itsGapPosition;
  2219.         long    textLength = itsTextLength;
  2220.         Handle    textHandle = itsTextHandle;
  2221.         
  2222.         if (newGapLength < gapLength) {
  2223.             BlockMove(*textHandle + gapPosition + gapLength,
  2224.                       *textHandle + gapPosition + newGapLength,
  2225.                       textLength - gapPosition);
  2226.             SetHandleSize(textHandle, textLength + newGapLength);
  2227.             itsGapLength = newGapLength;
  2228.         }
  2229.         else {
  2230.             ResizeHandleCanFail(textHandle, textLength + newGapLength);
  2231.             if (MemError() == noErr) {
  2232.                 BlockMove(*textHandle + gapPosition + gapLength,
  2233.                           *textHandle + gapPosition + newGapLength,
  2234.                           textLength - gapPosition);
  2235.                 itsGapLength = newGapLength;
  2236.             }
  2237.         }
  2238.         
  2239.     }
  2240. }
  2241.  
  2242.  
  2243. /******************************************************************************
  2244.  CloseGap
  2245.  
  2246.         Internal method to close the insertion gap.
  2247.  ******************************************************************************/
  2248.  
  2249. void    CPEditText::CloseGap()
  2250. {
  2251.     SetGapLength(0);
  2252.     SetGapPosition(0);
  2253. }
  2254.  
  2255. #endif // qPEUseInsertionGap
  2256.  
  2257.  
  2258. /******************************************************************************
  2259.  CheckInsertion
  2260.  
  2261.         Internal method to "preflight" an attempt to insert text and ensure
  2262.         that enough memory is available for the insertion to succeed.
  2263.         
  2264.         If useSelection is TRUE, the length of the selection is deducted
  2265.         from the total length.  If numInsertCRs is non-NULL, the number of
  2266.         return characters in the text being inserted is returned.  If
  2267.         useSelection is TRUE and numSelectionCRs is non-NULL, the number
  2268.         of return characters in the current selection is returned.
  2269.  ******************************************************************************/
  2270.  
  2271. void    CPEditText::CheckInsertion(
  2272.     Ptr            insertPtr,                // Ptr to text being inserted
  2273.     long        insertLen,                // Length of text being inserted
  2274.     Boolean        useSelection,            // Compensate for selection length?
  2275.     long        *numInsertCRs,            // Number of CRs in inserted text
  2276.     long        *numSelectionCRs)        // Number of CRs in selection
  2277. {
  2278.     long        prevTextLen;            // Previous length of text handle
  2279.     long        prevLineStartsLen;        // Previous length of line starts
  2280.     long        newTextLen;                // New length of text handle
  2281.     long        newLineStartsLen;        // New length of line starts
  2282.     long        insertCRsCount;            // Number of CRs in inserted text
  2283.     
  2284.     // Save the current lengths of the text and line starts handles
  2285.     prevTextLen = GetHandleSize(itsTextHandle);
  2286.     prevLineStartsLen = GetHandleSize((Handle)itsLineStarts);
  2287.     
  2288.     // Count the number of return characters in the text being inserted
  2289.     if ((insertPtr != NULL) && (insertLen > 0))
  2290.         insertCRsCount = CountCRs(insertPtr, insertLen);
  2291.     else
  2292.         insertCRsCount = 0;
  2293.     
  2294.     // Compute the new lengths of the handles
  2295.     newTextLen = prevTextLen + insertLen;
  2296.     newLineStartsLen = prevLineStartsLen + insertCRsCount * sizeof(long);
  2297.     if (numInsertCRs != NULL)
  2298.         *numInsertCRs = insertCRsCount;
  2299.     
  2300.     // If we're taking the selection into account, adjust the new lengths
  2301.     // of the handles accordingly
  2302.     if (useSelection) {
  2303.         long    crCount = CountRangeCRs(itsSelStart, itsSelEnd);
  2304.         
  2305.         newTextLen -= (itsSelEnd - itsSelStart);
  2306.         newLineStartsLen -= crCount * sizeof(long);
  2307.         
  2308.         if (numSelectionCRs != NULL)
  2309.             *numSelectionCRs = crCount;
  2310.     }
  2311.     
  2312.     // Attempt to resize the handles
  2313.     TRY {
  2314.         if (newTextLen > prevTextLen) {
  2315.             ResizeHandleCanFail(itsTextHandle, newTextLen);
  2316.             FailMemError();
  2317.         }
  2318.         
  2319.         if (newLineStartsLen > prevLineStartsLen) {
  2320.             ResizeHandleCanFail((Handle)itsLineStarts, newLineStartsLen);
  2321.             FailMemError();
  2322.             SetHandleSize((Handle)itsLineStarts, prevLineStartsLen);
  2323.         }
  2324.         
  2325.         if (newTextLen > prevTextLen)
  2326.             SetHandleSize(itsTextHandle, prevTextLen);
  2327.     }
  2328.     CATCH {
  2329.         SetHandleSize(itsTextHandle, prevTextLen);
  2330.         SetHandleSize((Handle)itsLineStarts, prevLineStartsLen);
  2331.     }
  2332.     ENDTRY
  2333. }
  2334.  
  2335.  
  2336. /******************************************************************************
  2337.  CountRangeCRs
  2338.  
  2339.         Internal method to count the number of carriage return characters
  2340.         in the given range of text.
  2341.  ******************************************************************************/
  2342.  
  2343. long    CPEditText::CountRangeCRs(long start, long end)
  2344. {
  2345.     Ptr        textP = *itsTextHandle;
  2346.     long    length = end - start;
  2347. #if qPEUseInsertionGap
  2348.     long    gapPosition = itsGapPosition;
  2349.     long    gapLength = itsGapLength;
  2350. #endif
  2351.     long    crCount;
  2352.     
  2353.     if (start >= end)
  2354.         crCount = 0;
  2355.     else if ((start == 0) && (end == itsTextLength))
  2356.         crCount = itsNumLines - 1;
  2357. #if qPEUseInsertionGap
  2358.     else if (end <= gapPosition)
  2359.         crCount = CountCRs(textP + start, length);
  2360.     else if (start >= gapPosition)
  2361.         crCount = CountCRs(textP + start + gapLength, length);
  2362.     else {
  2363.         long    lenBeforeGap = gapPosition - start;
  2364.         crCount = CountCRs(textP + start, lenBeforeGap);
  2365.         crCount += CountCRs(textP + start + gapLength + lenBeforeGap, length - lenBeforeGap);
  2366.     }
  2367. #else
  2368.     else
  2369.         crCount = CountCRs(textP + start, length);
  2370. #endif
  2371.     
  2372.     return crCount;
  2373. }
  2374.  
  2375.  
  2376. /******************************************************************************
  2377.  InsertText
  2378.  
  2379.         Internal method to insert a copy of the given text at the start of
  2380.         the selection.  Called by InsertTextPtr and ReplaceSelection.
  2381.         Assumes that CheckInsertion has been called to assure that the
  2382.         insertion will succeed.
  2383.  ******************************************************************************/
  2384.  
  2385. void    CPEditText::InsertText(
  2386.     Ptr            insertPtr,                // Pointer to text to insert
  2387.     long        insertLen,                // Length of text to insert
  2388.     long        numInsertCRs,            // Number of CRs in inserted text
  2389.     Boolean        redraw)                    // TRUE to redraw text
  2390. {
  2391.     Ptr            textP;
  2392.     long        selStart = itsSelStart;
  2393.     long        selEnd = itsSelEnd;
  2394.     
  2395.     HideSelection(kHideSelection, kRedraw);
  2396.     
  2397. #if qPEUseInsertionGap
  2398.     // Check if the text being inserted will fit into the gap
  2399.     // If not, resize the text handle to accomodate the new text
  2400.     if ((selStart == itsGapPosition) && (insertLen <= itsGapLength)) {
  2401.         textP = *itsTextHandle;
  2402.         if (insertLen == 1)                // Special case to avoid BlockMove overhead
  2403.             *(textP + selStart) = *insertPtr;
  2404.         else
  2405.             BlockMove(insertPtr, textP + selStart, insertLen);
  2406.         
  2407.         itsGapPosition += insertLen;
  2408.         itsGapLength -= insertLen;
  2409.     }
  2410.     else {
  2411.         CloseGap();
  2412. #endif
  2413.         SetHandleSize(itsTextHandle, itsTextLength + insertLen);
  2414.         FailMemError();
  2415.         textP = *itsTextHandle;
  2416.         BlockMove(textP + selStart, textP + selStart + insertLen, itsTextLength - selStart);
  2417.         BlockMove(insertPtr, textP + selStart, insertLen);
  2418. #if qPEUseInsertionGap
  2419.     }
  2420. #endif
  2421.         
  2422.     itsTextLength += insertLen;
  2423.     
  2424.     // Adjust the line starts and redraw the new text if necessary
  2425.     AdjustLineStarts(selStart, insertLen, numInsertCRs);
  2426.     if (!redraw)
  2427.         RefreshTextAfter(selStart, (numInsertCRs == 0));
  2428.         
  2429.     // Set the selection to the end of the inserted text
  2430.     SetSelection(selStart + insertLen, selEnd + insertLen, kNoRedraw);
  2431.     
  2432.     // Redraw the entire pane if requested
  2433.     if (redraw)
  2434.         Refresh();
  2435. }
  2436.  
  2437.  
  2438. /******************************************************************************
  2439.  DeleteText
  2440.  
  2441.         Internal method to delete a range of characters.  Called by
  2442.         DeleteTextRange and ReplaceSelection.
  2443.  ******************************************************************************/
  2444.  
  2445. void    CPEditText::DeleteText(
  2446.     long        start,
  2447.     long        end,
  2448.     long        numDeleteCRs,
  2449.     Boolean        redraw)
  2450. {
  2451.     long        length;
  2452.     long        selStart;
  2453.     long        selEnd;
  2454.     
  2455.     length = end - start;
  2456.     if (length > 0) {
  2457.         
  2458. #if qPEUseInsertionGap
  2459.         // Check if we can delete the selected text by simply adding it to the insertion gap
  2460.         if ((itsGapLength == 0) || (start == itsGapPosition) || (end == itsGapPosition)) {
  2461.             itsGapPosition = start;
  2462.             itsGapLength += length;
  2463.             itsTextLength -= length;
  2464.         }
  2465.         else {
  2466.             // Close up the insertion gap and remove the selected text from the text handle
  2467.             CloseGap();
  2468. #endif
  2469.             BlockMove(*itsTextHandle + end, *itsTextHandle + start, itsTextLength - end);
  2470.             itsTextLength -= length;
  2471.             SetHandleSize(itsTextHandle, itsTextLength);
  2472. #if qPEUseInsertionGap
  2473.         }
  2474. #endif
  2475.                 
  2476.         // Adjust the line starts array
  2477.         AdjustLineStarts(start, -length, -numDeleteCRs);
  2478.         
  2479.         // Adjust the current selection
  2480.         GetSelection(&selStart, &selEnd);
  2481.         if (selStart >= start) {
  2482.             if (selStart >= end)
  2483.                 selStart -= length;
  2484.             else
  2485.                 selStart = start;
  2486.         }
  2487.         if (selEnd >= start) {
  2488.             if (selEnd >= end)
  2489.                 selEnd -= length;
  2490.             else
  2491.                 selEnd = selStart;
  2492.         }
  2493.         SetSelection(selStart, selEnd, kNoRedraw);
  2494.  
  2495.         // Refresh the necessary text if requested
  2496.         if (redraw)
  2497.             RefreshTextAfter(start, (numDeleteCRs == 0));
  2498.     }
  2499. }
  2500.  
  2501.  
  2502. /******************************************************************************
  2503.  RefreshTextAfter
  2504.  
  2505.         Internal method to redraw the text following the specified character.
  2506.  ******************************************************************************/
  2507.  
  2508. void    CPEditText::RefreshTextAfter(
  2509.     long        afterPos,
  2510.     Boolean        refreshOnlyLine)
  2511. {
  2512.     long        startLine;
  2513.     long        endLine;
  2514.     long        vertInset = VertInset();
  2515.     LongRect    lr;
  2516.     Rect        r;
  2517.     
  2518.     Prepare();
  2519.     
  2520.     // Redraw the text after the given offset on the line
  2521.     startLine = FindLine(afterPos);
  2522.     if (refreshOnlyLine)
  2523.         endLine = startLine;
  2524.     else {
  2525.         endLine = (frame.bottom - vertInset) / itsLineHeight;
  2526.         endLine = Min(endLine, itsNumLines - 1);
  2527.     }
  2528.     
  2529.     DrawLineRange(startLine, endLine, afterPos - GetLineStart(startLine), kEraseText);
  2530.         
  2531.     // Erase the area below the last text line, if necessary
  2532.     if (!refreshOnlyLine) {
  2533.         SetLongRect(&lr, frame.left, (endLine + 1) * itsLineHeight + vertInset, frame.right, frame.bottom);
  2534.         if (lr.bottom > lr.top) {
  2535.             FrameToQDR(&lr, &r);
  2536.             EraseRect(&r);
  2537.         }
  2538.     }
  2539. }
  2540.  
  2541.     
  2542. /******************************************************************************
  2543.  DrawLineRange
  2544.  
  2545.         Internal method to draw the specified range of text lines.  Assumes
  2546.         the pane has been prepared.
  2547.  ******************************************************************************/
  2548.  
  2549. void    CPEditText::DrawLineRange(
  2550.     long        startLine,
  2551.     long        endLine,
  2552.     long        startLineOffset,
  2553.     Boolean        erase)
  2554. {
  2555.     Boolean            showInvisibles = fShowInvisibles;
  2556.     char            textHState;
  2557.     register char    tab;
  2558.     Handle            invisH;
  2559.     long            line;
  2560.     long            lineStart;
  2561.     long            lineEnd;
  2562.     long            firstChar;
  2563.     long            boundsHExtent;
  2564.     long            horizInset = HorizInset();
  2565. #if qPEUseInsertionGap
  2566.     long            gapPosition = itsGapPosition;
  2567.     long            gapLength = itsGapLength;
  2568. #endif
  2569.     LongPt            textPt;
  2570.     LongRect        lr;
  2571.     Point            penPt;
  2572.     Point            startPt;
  2573. #if qPEUseInsertionGap
  2574.     Ptr                gapP;
  2575. #endif
  2576.     Rect            eraseRect;
  2577.     RgnHandle        clipRgn;
  2578.     register Ptr    textP;
  2579.     register short    numChars;
  2580.     register short    index;
  2581.     register short    tabWidth = itsTabWidth;
  2582.     ShortHandle        widthsH = NULL;
  2583.     short            width;
  2584.     short            *widthsP;
  2585.     short            fontAscent = itsFontAscent;
  2586.     short            lineHeight = itsLineHeight;
  2587.     
  2588.     // Lock down the text handle
  2589.     textHState = HGetState(itsTextHandle);
  2590.     HLock(itsTextHandle);
  2591.  
  2592.     // If we are erasing, save the current clipping region
  2593.     if (erase) {
  2594.         GetClip(cSaveClipRgn);
  2595.         clipRgn = NewRgn();
  2596.     }
  2597.     
  2598.     // Compute the vertical coordinate for the first line of text
  2599.     textPt.v = startLine * lineHeight + fontAscent + VertInset();
  2600.     
  2601.     // Draw the specified lines of text
  2602.     for (line = startLine; line <= endLine; ++line) {
  2603.     
  2604.         // Compute the number of characters to draw
  2605.         lineStart = GetLineStart(line);
  2606.         lineEnd = GetLineEnd(line);
  2607.         
  2608.         firstChar = lineStart + startLineOffset;
  2609.         numChars = lineEnd - firstChar;
  2610.         
  2611.         if ((line == startLine) && (startLineOffset > 0)) {
  2612.             widthsH = MeasureLineWidths(line);
  2613.             textPt.h = (*widthsH)[startLineOffset] + horizInset;
  2614.         }
  2615.         else {
  2616.             widthsH = NULL;
  2617.             textPt.h = horizInset;
  2618.         }
  2619.         
  2620.         FrameToQD(&textPt, &startPt);
  2621.         
  2622.         // Erase the background behind the text, if desired
  2623.         if (erase) {
  2624.             SetLongRect(&lr, (line == startLine && startLineOffset > 0 ? textPt.h : frame.left),
  2625.                         textPt.v - fontAscent, frame.right, textPt.v - fontAscent + lineHeight);            
  2626.             FrameToQDR(&lr, &eraseRect);
  2627.             
  2628.             // Adjust the clipping region and set things so that the entire line
  2629.             // is redrawn -- this is so that italic characters don't get clipped
  2630.             RectRgn(clipRgn, &eraseRect);
  2631.             SectRgn(clipRgn, cSaveClipRgn, clipRgn);
  2632.             SetClip(clipRgn);
  2633.             
  2634.             firstChar = lineStart;
  2635.             numChars = lineEnd - lineStart;
  2636.             
  2637.             textPt.h = horizInset;
  2638.             FrameToQD(&textPt, &startPt);
  2639.         }
  2640.         
  2641.         ForgetHandle(widthsH);
  2642.         
  2643.         // If invisible characters are visible, make a copy of the text
  2644.         // and convert all the invisible characters
  2645.         if (showInvisibles) {
  2646.             invisH = CopyTextRange(lineStart, lineEnd);
  2647.             numChars = ConvertInvisibles(invisH, numChars);
  2648.             HLock(invisH);
  2649.             textP = *invisH;
  2650. #if qPEUseInsertionGap
  2651.             gapP = NULL;
  2652.             gapPosition = gapLength = 0;
  2653. #endif
  2654.         }
  2655.         else {
  2656. #if qPEUseInsertionGap
  2657.             textP = *itsTextHandle;
  2658.             gapP = textP + gapPosition;
  2659.             textP += firstChar;
  2660.             if (firstChar > gapPosition)
  2661.                 textP += gapLength;
  2662. #else
  2663.             textP = *itsTextHandle + firstChar;
  2664. #endif
  2665.         }
  2666.         
  2667.         // Now draw the line of text
  2668.         
  2669.         FrameToQD(&textPt, &penPt);
  2670.         MoveTo(penPt.h, penPt.v);
  2671.         if (erase)
  2672.             EraseRect(&eraseRect);
  2673.         
  2674. #if THINK_C
  2675.         asm {
  2676.             clr.w    index                    ; reset index
  2677.             move.b    #kTab,tab                ; keep tab character in register for speed
  2678.             
  2679.         @Loop:
  2680.             cmp.w    numChars,index            ; have we drawn all the text?
  2681.             bge.s    @Done                    ; branch if we have
  2682. #if qPEUseInsertionGap
  2683.             cmpa.l    gapP,textP                ; are we at the gap?
  2684.             bne.s    @NotAtGap                ; branch if not
  2685.             
  2686.         @AtGap:
  2687.             bsr.s    @DrawText                ; else draw the text to the left of the gap
  2688.             adda.l    gapLength,textP            ; and skip over the gap
  2689.         
  2690.         @NotAtGap:
  2691. #endif
  2692.             addq.w    #1,index                ; else bump the index        
  2693.             cmp.b    (textP)+,tab            ; is character from text a tab?
  2694.             bne.s    @Loop                    ; loop if not
  2695.             
  2696.         @IsTab:
  2697.             subq.w    #1,textP                ; decrement ptr to text
  2698.             subq.w    #1,index                ; decrement # of characters to draw
  2699.             bsr.s    @DrawText                ; draw the text to the left of the tab
  2700.             
  2701.             movea.l    thePort,a0                ; get current GrafPtr
  2702.             move.l    OFFSET(GrafPort,pnLoc)(a0),penPt ; get pen location
  2703.             
  2704.             tst.b    showInvisibles            ; are we displaying invisible characters?
  2705.             beq.s    @NoInvis1                ; branch if not
  2706.             move.w    #kInvisTab,-(a7)        ; else push invisible tab character
  2707.             _DrawChar                        ; and draw it
  2708.             
  2709.         @NoInvis1:
  2710.             moveq    #0,d0                    ; clear out all of D0
  2711.             move.w    penPt.h,d0                ; get horiz pen location
  2712.             sub.w    startPt.h,d0            ; subtract starting location
  2713.             divu.w    tabWidth,d0                ; divide by tab width
  2714.             addq.w    #1,d0                    ; add one to tab width
  2715.             mulu.w    tabWidth,d0                ; get pixel offset
  2716.             add.w    startPt.h,d0            ; add starting location
  2717.             
  2718.             move.w    d0,-(a7)                ; push horiz coordinate
  2719.             move.w    penPt.v,-(a7)            ; push vert coordinate
  2720.             _MoveTo                            ; move pen location
  2721.             
  2722.             addq.w    #1,textP                ; bump ptr to text
  2723.             subq.w    #1,numChars                ; subtract 1 from length of text
  2724.             bra.s    @Loop                    ; and loop
  2725.     
  2726.         @DrawText:
  2727.             tst.w    index                    ; any text to draw?
  2728.             beq.s    @NoText                    ; skip if not
  2729.             
  2730.             movea.l    textP,a0                ; get ptr to next character
  2731.             suba.w    index,a0                ; subtract index
  2732.             move.l    a0,-(a7)                ; push ptr to text
  2733.             clr.w    -(a7)                    ; push offset into text
  2734.             move.w    index,-(a7)                ; push length of text to draw
  2735.             _DrawText                        ; call DrawText
  2736.             
  2737.             sub.w    index,numChars            ; subtract from length of text
  2738.             clr.w    index                    ; reset index to 0
  2739.         @NoText:
  2740.             rts
  2741.     
  2742.         @Done:
  2743.             bsr.s    @DrawText                ; draw rest of text
  2744.         }
  2745. #else
  2746. #if qPEUseInsertionGap
  2747.         AsmDrawLineRange(textP, numChars, tabWidth, gapP, gapLength, startPt, showInvisibles);
  2748. #else
  2749.         AsmDrawLineRange(textP, numChars, tabWidth, NULL, 0, startPt, showInvisibles);
  2750. #endif
  2751. #endif
  2752.                     
  2753.         // Dispose the temporary handle for invisible characters
  2754.         if (showInvisibles)
  2755.             DisposHandle(invisH);
  2756.             
  2757.         // Increment the text vertical coordinate
  2758.         textPt.v += lineHeight;
  2759.     }
  2760.     
  2761.     // Restore the previous clipping region and the previous state of the text handle
  2762.     if (erase) {
  2763.         SetClip(cSaveClipRgn);    
  2764.         DisposeRgn(clipRgn);
  2765.     }
  2766.     
  2767.     HSetState(itsTextHandle, textHState);
  2768. }
  2769.  
  2770.  
  2771. /******************************************************************************
  2772.  HiliteTextRange
  2773.  
  2774.         Internal method to highlight the given range of text.  Assumes
  2775.         the pane has been prepared.
  2776.  ******************************************************************************/
  2777.  
  2778. void    CPEditText::HiliteTextRange(long start, long end)
  2779. {
  2780.     Boolean        isActive = fReallyActive;
  2781.     long        startLine;
  2782.     long        endLine;
  2783.     LongPt        startPt;
  2784.     LongPt        endPt;
  2785.     LongRect    hiliteRect;
  2786.     PenState    penState;
  2787.     Rect        qdRect;
  2788.     RgnHandle    hiliteRgn;
  2789.     RgnHandle    rectRgn;
  2790.     short        hSpan;
  2791.     short        vSpan;
  2792.     short        fontAscent = itsFontAscent;
  2793.     short        lineHeight = itsLineHeight;
  2794.     
  2795.     if (isActive || fOutlineHilite) {
  2796.         
  2797.         // Outline the selection range if the pane is not active
  2798.         if (!isActive) {
  2799.             hiliteRgn = NewRgn();
  2800.             rectRgn = NewRgn();
  2801.         }
  2802.         
  2803.         // Get the starting and ending selection lines and the number of lines
  2804.         // spanned by the frame
  2805.         startLine = FindLine(start);
  2806.         endLine = FindLine(end);
  2807.         GetFrameSpan(&hSpan, &vSpan);
  2808.         
  2809.         // Take a quick exit if the selection is not visible within the frame span
  2810.         if ((startLine >= position.v + vSpan) || (endLine < position.v))
  2811.             return;
  2812.         
  2813.         // Adjust the starting and ending selection lines if they are outside the frame
  2814.         if (startLine < position.v) {
  2815.             startLine = position.v;
  2816.             start = GetLineStart(startLine);
  2817.         }
  2818.         
  2819.         if (endLine > position.v + vSpan) {
  2820.             endLine = position.v + vSpan;
  2821.             end = GetLineEnd(endLine);
  2822.         }
  2823.         
  2824.         // Get the frame coordinates corresponding to the
  2825.         // start and end of the selection range
  2826.         GetCharPoint(start, &startPt);
  2827.         GetCharPoint(end, &endPt);
  2828.         
  2829.         // Adjust the horizontal coordinates if the either of the starting
  2830.         // or ending characters lies at the beginning of a line
  2831.         if (start == GetLineStart(startLine))
  2832.             startPt.h = frame.left;
  2833.         if (end == GetLineStart(endLine))
  2834.             endPt.h = frame.left;
  2835.         
  2836.         // Check for and handle a multiple-line selection range
  2837.         if (startPt.v != endPt.v) {
  2838.         
  2839.             // Highlight the first line of the selection range
  2840.             SetLongRect(&hiliteRect, startPt.h, startPt.v - fontAscent, frame.right, startPt.v - fontAscent + lineHeight);
  2841.             FrameToQDR(&hiliteRect, &qdRect);
  2842.             
  2843.             if (isActive)
  2844.                 HiliteHook(&qdRect);
  2845.             else {
  2846.                 qdRect.left -= 1;
  2847.                 RectRgn(rectRgn, &qdRect);
  2848.                 UnionRgn(rectRgn, hiliteRgn, hiliteRgn);
  2849.             }
  2850.             
  2851.             // Highlight the middle lines of the selection range
  2852.             hiliteRect.left = frame.left;
  2853.             
  2854.             if (isActive) {
  2855.                 FrameToQDR(&hiliteRect, &qdRect);
  2856.                 
  2857.                 while (++startLine < endLine) {
  2858.                     qdRect.top += lineHeight;
  2859.                     qdRect.bottom += lineHeight;
  2860.                     HiliteHook(&qdRect);
  2861.                 }
  2862.             }
  2863.             else {
  2864.                 hiliteRect.top += lineHeight;
  2865.                 hiliteRect.left -= 1;
  2866.                 hiliteRect.bottom = endPt.v - fontAscent + 1;
  2867.                 FrameToQDR(&hiliteRect, &qdRect);
  2868.                 RectRgn(rectRgn, &qdRect);
  2869.                 UnionRgn(rectRgn, hiliteRgn, hiliteRgn);
  2870.             }
  2871.             
  2872.             SetLongPt(&startPt, hiliteRect.left, endPt.v);
  2873.         }
  2874.         
  2875.         // Hilite the last part of the selection range
  2876.         SetLongRect(&hiliteRect, startPt.h, endPt.v - fontAscent, endPt.h, endPt.v - fontAscent + lineHeight);
  2877.         FrameToQDR(&hiliteRect, &qdRect);
  2878.         
  2879.         if (isActive)
  2880.             HiliteHook(&qdRect);
  2881.         else {
  2882.             if (qdRect.right > qdRect.left + 1) {
  2883.                 qdRect.bottom += 1;
  2884.                 RectRgn(rectRgn, &qdRect);
  2885.                 UnionRgn(rectRgn, hiliteRgn, hiliteRgn);
  2886.             }
  2887.             
  2888.             // Intersect the highlight region with the pane frame
  2889.             FrameToQDR(&frame, &qdRect);
  2890.             RectRgn(rectRgn, &qdRect);
  2891.             SectRgn(rectRgn, hiliteRgn, hiliteRgn);
  2892.             DisposeRgn(rectRgn);
  2893.             
  2894.             // Outline the highlight region
  2895.             GetPenState(&penState);
  2896.             PenNormal();
  2897.             PenMode(patXor);
  2898.             FrameRgn(hiliteRgn);
  2899.             DisposeRgn(hiliteRgn);
  2900.             SetPenState(&penState);
  2901.         }
  2902.     }
  2903. }
  2904.  
  2905.  
  2906. /******************************************************************************
  2907.  GetTextWidth
  2908.     
  2909.         Internal method to return the number of pixels between the
  2910.         specified characters on a given line.
  2911.  ******************************************************************************/
  2912.  
  2913. short    CPEditText::GetTextWidth(long line, short startPos, short endPos)
  2914. {
  2915.     ShortHandle    widthsH;
  2916.     short        width;
  2917.     
  2918.     if (endPos > startPos) {
  2919.         widthsH = MeasureLineWidths(line);
  2920.         width = (*widthsH)[endPos] - (*widthsH)[startPos];
  2921.         DisposHandle((Handle)widthsH);
  2922.     }
  2923.     else
  2924.         width = 0;
  2925.     
  2926.     return width;
  2927. }
  2928.  
  2929.  
  2930. /******************************************************************************
  2931.  MeasureLineWidths
  2932.     
  2933.         Internal method to compute and return an array of pixel distances
  2934.         for the given line.
  2935.  ******************************************************************************/
  2936.  
  2937. ShortHandle    CPEditText::MeasureLineWidths(long line)
  2938. {
  2939.     return MeasureTextWidths(GetLineStart(line), GetLineEnd(line), MAXINT);
  2940. }
  2941.  
  2942.  
  2943. /******************************************************************************
  2944.  MeasureTextWidths
  2945.     
  2946.         Internal method to compute and return an array of pixel distances
  2947.         for the text between the given offsets.  The maxWidth parameter
  2948.         specifies the maximum pixel width that the client is interested in.
  2949.  ******************************************************************************/
  2950.  
  2951. ShortHandle    CPEditText::MeasureTextWidths(long startPos, long endPos, short maxWidth)
  2952. {
  2953.     char            textHState;
  2954. #if THINK_C
  2955.     register char    tab;
  2956. #endif
  2957.     Handle            invisH = NULL;
  2958. #if qPEUseInsertionGap
  2959.     long            gapPosition = itsGapPosition;
  2960.     long            gapLength = itsGapLength;
  2961.     Ptr                gapP;
  2962. #endif
  2963.     register Ptr    textP;
  2964.     register short    numChars;
  2965. #if THINK_C
  2966.     register short    index;
  2967.     short            lastWidth;
  2968. #endif
  2969.     register short    tabWidth = itsTabWidth;
  2970.     register short    *widthsP;
  2971.     ShortHandle        widthsH;
  2972.             
  2973.     // Lock down the text handle and allocate space for the widths array
  2974.     textHState = HGetState(itsTextHandle);
  2975.     HLock(itsTextHandle);
  2976.     
  2977.     numChars = (short)(endPos - startPos);
  2978.     widthsH = (ShortHandle)NewHandle((numChars + 1) * sizeof(short));
  2979.     HLock((Handle)widthsH);
  2980.     widthsP = *widthsH;
  2981.     
  2982.     // If "show invisible characters" is enabled, make a copy of the text
  2983.     // and convert all the invisible characters
  2984.     if (fShowInvisibles) {
  2985.         invisH = CopyTextRange(startPos, endPos);
  2986.         numChars = ConvertInvisibles(invisH, numChars);
  2987.         HLock(invisH);
  2988.         textP = *invisH;
  2989. #if qPEUseInsertionGap
  2990.         gapP = NULL;
  2991.         gapPosition = gapLength = 0;
  2992. #endif
  2993.     }
  2994.     else {
  2995. #if qPEUseInsertionGap
  2996.         textP = *itsTextHandle;
  2997.         gapP = textP + gapPosition;
  2998.         textP += startPos;
  2999.         if (startPos >= gapPosition)
  3000.             textP += gapLength;
  3001. #else
  3002.         textP = *itsTextHandle + startPos;
  3003. #endif
  3004.     }
  3005.         
  3006.     // Measure the text and save the pixel widths
  3007.     Prepare();
  3008.     
  3009. #if THINK_C
  3010.     asm {
  3011.         clr.w    index                    ; reset index
  3012.         clr.w    lastWidth                ; set lastWidth to zero
  3013.         move.b    #kTab,tab                ; keep tab character in register for speed
  3014.         
  3015.     @Loop:
  3016.         cmp.w    numChars,index            ; have we measured all the text?
  3017.         bge.s    @Done                    ; branch if we have
  3018. #if qPEUseInsertionGap
  3019.         cmpa.l    gapP,textP                ; are we at the gap?
  3020.         bne.s    @NotAtGap                ; branch if not
  3021.         
  3022.     @AtGap:
  3023.         bsr.s    @MeasTxt                ; else measure the text to the left of the gap
  3024.         adda.l    gapLength,textP            ; and skip over the gap
  3025.         
  3026.     @NotAtGap:
  3027. #endif
  3028.         addq.w    #1,index                ; else bump the index        
  3029.         cmp.b    (textP)+,tab            ; is character from text a tab?
  3030.         bne.s    @Loop                    ; loop if not
  3031.         
  3032.     @IsTab:
  3033.         subq.w    #1,textP                ; decrement ptr to text
  3034.         subq.w    #1,index                ; decrement # of characters to measure
  3035.         bsr.s    @MeasTxt                ; measure the text to the left
  3036.         
  3037.         moveq    #0,d0                    ; clear out all of d0
  3038.         move.w    lastWidth,d0            ; get total width of text so far
  3039.         divu.w    tabWidth,d0                ; divide by tab width
  3040.         addq.w    #1,d0                    ; add one to tab width
  3041.         mulu.w    tabWidth,d0                ; compute width at next tab stop
  3042.         addq.w    #2,widthsP                ; bump widthsP
  3043.         move.w    d0,(widthsP)            ; save width of tab in widths array
  3044.         move.w    d0,lastWidth            ; remember new total width
  3045.         
  3046.         addq.w    #1,textP                ; bump ptr to text
  3047.         subq.w    #1,numChars                ; decrement length of text
  3048.         bra.s    @Loop                    ; and loop
  3049.             
  3050.     @MeasTxt:
  3051.         move.w    index,-(a7)                ; push number of characters to measure
  3052.         movea.l    textP,a0                ; get ptr to next character in text
  3053.         suba.w    index,a0                ; subtract index
  3054.         move.l    a0,-(a7)                ; push ptr to text to measure
  3055.         move.l    widthsP,-(a7)            ; push ptr to current position in widths array
  3056.         _MeasureText                    ; measure the text
  3057.         
  3058.         move.w    lastWidth,d0            ; keep lastWidth in d0
  3059.         move.w    index,d1                ; loop index in d1
  3060.     @WidthLoop:
  3061.         add.w    d0,(widthsP)+            ; add lastWidth to element of widths array
  3062.         dbra    d1,@WidthLoop            ; and loop
  3063.         
  3064.         move.w    -(widthsP),lastWidth    ; save width of last character
  3065.         sub.w    index,numChars            ; decrement length of text
  3066.         clr.w    index                    ; reset character count
  3067.         rts                                ; and return to caller
  3068.  
  3069.     @Done:
  3070.         bsr.s    @MeasTxt                ; measure the rest of the text
  3071.     }
  3072. #else
  3073. #if qPEUseInsertionGap
  3074.     AsmMeasureTextWidths(textP, numChars, tabWidth, gapP, gapLength, widthsP, maxWidth);
  3075. #else
  3076.     AsmMeasureTextWidths(textP, numChars, tabWidth, NULL, 0, widthsP, maxWidth);
  3077. #endif
  3078. #endif
  3079.     
  3080.     // Clean up
  3081.     HUnlock((Handle)widthsH);
  3082.     HSetState(itsTextHandle, textHState);
  3083.     ForgetHandle(invisH);
  3084.     
  3085.     return widthsH;
  3086. }
  3087.  
  3088.  
  3089. /******************************************************************************
  3090.  ConvertInvisibles
  3091.  
  3092.         Internal method to convert any invisible characters in the given
  3093.         text to their corresponding visible characters.  The handle may
  3094.         be resized if necessary, so the method returns the number of
  3095.         characters in the resized handle.
  3096.  ******************************************************************************/
  3097.  
  3098. short    CPEditText::ConvertInvisibles(Handle invisH, short numChars)
  3099. {
  3100.     register Ptr    invisP = *invisH;
  3101.     register short    count = numChars;
  3102.     
  3103.     while (--count >= 0) {
  3104.         if (*invisP <= kSpace) {
  3105.             switch (*invisP) {
  3106.                 case kSpace:
  3107.                     *invisP = kInvisSpace;
  3108.                     break;
  3109.                 case kReturn:
  3110.                     *invisP = kInvisReturn;
  3111.                     break;
  3112.                 case kLineFeed:
  3113.                     *invisP = kInvisLineFeed;
  3114.                     break;
  3115.                 case kFormFeed:
  3116.                     *invisP = kInvisFormFeed;
  3117.                     break;
  3118.                 case kTab:
  3119.                     // Don't convert tabs here
  3120.                     break;
  3121.                 default:
  3122.                     *invisP = kInvisOther;
  3123.                     break;
  3124.             }
  3125.         }
  3126.         
  3127.         ++invisP;
  3128.     }
  3129.     
  3130.     return numChars;            // since we don't yet resize the handle
  3131. }
  3132.  
  3133.  
  3134. /******************************************************************************
  3135.  AdjustBounds
  3136.  
  3137.         Internal method to adjust the bounds of a PEditText panorama.
  3138.         Performed whenever something happens which can affect the number of
  3139.         lines of text or the line width.
  3140.  ******************************************************************************/
  3141.  
  3142. void    CPEditText::AdjustBounds()
  3143. {
  3144.     LongRect    newBounds;
  3145.     
  3146.     newBounds.top = 0;
  3147.     newBounds.left = 0;
  3148.     newBounds.bottom = itsNumLines;
  3149.     newBounds.right = (kDefaultBoundsWidth - 1) / hScale + 1;
  3150.     SetBounds(&newBounds);
  3151. }
  3152.     
  3153.  
  3154. /******************************************************************************
  3155.  CalcLineHeight
  3156.  
  3157.         Internal method to calculate the line height and font ascent.
  3158.         Called by SetFontNumber, SetFontSize, SetFontStyle and SetSpacingCmd.
  3159.  ******************************************************************************/
  3160.  
  3161. void    CPEditText::CalcLineHeight()
  3162. {
  3163.     FontInfo        macFontInfo;
  3164.     
  3165.     // Update itsLineHeight, itsFontAscent and itsMaxCharWidth
  3166.     GetMacFontInfo(&macFontInfo);
  3167.     itsLineHeight = macFontInfo.ascent + macFontInfo.descent + macFontInfo.leading;
  3168.     itsFontAscent = macFontInfo.ascent;
  3169.     itsMaxCharWidth = macFontInfo.widMax;
  3170.     
  3171.     if (itsSpacingCmd == cmd1HalfSpace) {
  3172.         itsLineHeight *= 3;
  3173.         itsLineHeight /= 2;
  3174.     }
  3175.     else if (itsSpacingCmd == cmdDoubleSpace)
  3176.         itsLineHeight *= 2;
  3177.     
  3178.     CalcTabWidth();
  3179.     
  3180.     // Refresh the contents of the pane
  3181.     Refresh();
  3182.     SetScales(itsMaxCharWidth, itsLineHeight);
  3183.     AdjustBounds();
  3184.     SetWholeLines(wholeLines);
  3185.     Refresh();
  3186. }
  3187.  
  3188.  
  3189. /******************************************************************************
  3190.  CalcTabWidth
  3191.  
  3192.         Internal method to calculate the width of a tab.  Called by
  3193.         SetTabSpaces and CalcLineHeight.
  3194.  ******************************************************************************/
  3195.  
  3196. void    CPEditText::CalcTabWidth()
  3197. {
  3198.     Prepare();
  3199.     itsTabWidth = itsTabSpaces * CharWidth(kSpace);
  3200. }
  3201.  
  3202.  
  3203. /******************************************************************************
  3204.  CalcLineStarts
  3205.  
  3206.         Internal method to calculate the line starts array.
  3207.  ******************************************************************************/
  3208.  
  3209. void    CPEditText::CalcLineStarts()
  3210. {
  3211.     Boolean            origAlloc;
  3212.     register Byte    cr = kReturn;
  3213.     char            textHState;
  3214.     register long    charPos;
  3215.     register long    textLength = itsTextLength;
  3216.     register long    *lineStarts;
  3217.     register long    numLines;
  3218. #if qPEUseInsertionGap
  3219.     long            gapLength = itsGapLength;
  3220.     long            gapPosition = itsGapPosition;
  3221.     register Ptr    gapP;
  3222. #endif
  3223.     register Ptr    textP;
  3224.     unsigned long    startTicks = TickCount();
  3225.     
  3226.     // Initialize our pointer to the text
  3227.     textHState = HGetState(itsTextHandle);
  3228.     HLock(itsTextHandle);
  3229.     textP = *itsTextHandle;
  3230. #if qPEUseInsertionGap
  3231.     gapP = textP + gapPosition;
  3232. #endif
  3233.         
  3234.     // Count the number of lines in the text
  3235.     charPos = itsTextLength;
  3236.     numLines = 1;
  3237.     
  3238.     while (--charPos >= 0) {
  3239. #if qPEUseInsertionGap
  3240.         if (textP == gapP)
  3241.             textP += gapLength;
  3242. #endif
  3243.         if (*textP++ == cr)
  3244.             ++numLines;
  3245.     }
  3246.     
  3247.     // Reallocate the line starts array
  3248.         
  3249.     // Here we intentionally make resizing the line starts handle
  3250.     // a critical operation so that it is less likely to fail.
  3251.     // The rationale is that resizing the text handle is far more
  3252.     // likely to exceed available memory than resizing the line
  3253.     // starts handle (since the line starts handle is normally much
  3254.     // smaller), and falling back at this point is somewhat tricky
  3255.     // (although by no means impossible.)
  3256.     
  3257.     origAlloc = SetAllocation(kAllocCantFail);
  3258.     SetHandleSize((Handle)itsLineStarts, numLines * sizeof(long));
  3259.     (void)SetAllocation(origAlloc);
  3260.     FailMemError();
  3261.     
  3262.     // Update the number of lines
  3263.     itsNumLines = numLines;
  3264.     
  3265.     // Build the line starts array
  3266.     lineStarts = *itsLineStarts + 1;
  3267.     textP = *itsTextHandle;
  3268.     charPos = 0;
  3269.     
  3270.     while (++charPos <= textLength) {
  3271. #if qPEUseInsertionGap
  3272.         if (textP == gapP)
  3273.             textP += gapLength;
  3274. #endif
  3275.         if (*textP++ == cr)
  3276.             *lineStarts++ = charPos;
  3277.     }
  3278.     
  3279.     HSetState(itsTextHandle, textHState);
  3280.     
  3281.     // Adjust the panorama bounds to match the number of lines in the text
  3282.     AdjustBounds();
  3283. }
  3284.  
  3285.  
  3286. /******************************************************************************
  3287.  AdjustLineStarts
  3288.  
  3289.         Internal method to increment the line starts by the given amount,
  3290.         starting at the given line.
  3291.  ******************************************************************************/
  3292.  
  3293. void    CPEditText::AdjustLineStarts(long startChar, register long numCharsDelta, long numLinesDelta)
  3294. {
  3295.     register Byte    cr = kReturn;
  3296.     register long    *lineStarts;
  3297.     register long    count;
  3298.     long            startLine;
  3299.     register Ptr    textP;
  3300. #if qPEUseInsertionGap
  3301.     register Ptr    gapP;
  3302. #endif
  3303.         
  3304.     // Find the line corresponding to the starting character offset
  3305.     startLine = FindLine(startChar) + 1;
  3306.     
  3307.     // Adjust the line starts array depending on the change in the number of lines
  3308.     if (numLinesDelta == 0) {
  3309.         
  3310.         // Bump the values in the line starts array, starting at startLine
  3311.         lineStarts = *itsLineStarts + startLine;
  3312.         count = itsNumLines - startLine + 1;
  3313.         while (--count > 0)
  3314.             *lineStarts++ += numCharsDelta;
  3315.     }
  3316.     else if (numLinesDelta > 0) {
  3317.         Boolean    origAlloc;
  3318. #if qPEUseInsertionGap
  3319.         long    gapPosition = itsGapPosition;
  3320.         long    gapLength = itsGapLength;
  3321. #endif
  3322.                 
  3323.         // Insert new entries in the line starts array starting at startLine
  3324.         // (See comment in CalcLineStarts() method above for rationale here)
  3325.         origAlloc = SetAllocation(kAllocCantFail);
  3326.         SetHandleSize((Handle)itsLineStarts, (itsNumLines + numLinesDelta) * sizeof(long));
  3327.         (void)SetAllocation(origAlloc);
  3328.         FailMemError();
  3329.         
  3330.         if (startLine < itsNumLines)
  3331.             BlockMove(*itsLineStarts + startLine, *itsLineStarts + startLine + numLinesDelta, (itsNumLines - startLine) * sizeof(long));
  3332.         itsNumLines += numLinesDelta;
  3333.         
  3334.         // Calculate values for the new entries
  3335.         lineStarts = *itsLineStarts + startLine;
  3336.         textP = *itsTextHandle;
  3337. #if qPEUseInsertionGap
  3338.         gapP = textP + gapPosition;
  3339. #endif
  3340.         textP += startChar;
  3341. #if qPEUseInsertionGap
  3342.         if (startChar >= gapPosition)
  3343.             textP += gapLength;
  3344. #endif
  3345.                     
  3346.         count = numCharsDelta;
  3347.         while (--count >= 0) {
  3348. #if qPEUseInsertionGap
  3349.             if (textP == gapP)
  3350.                 textP += gapLength;
  3351. #endif
  3352.             if (*textP++ == cr)
  3353.                 *lineStarts++ = startChar + (numCharsDelta - count);
  3354.         }
  3355.         
  3356.         // Adjust the values of the remaining entries
  3357.         count = itsNumLines - (startLine + numLinesDelta);
  3358.         while (--count >= 0)
  3359.             *lineStarts++ += numCharsDelta;
  3360.     }
  3361.     else {    // numLinesDelta < 0
  3362.     
  3363.         // Delete entries from the line starts array starting at startLine
  3364.         itsNumLines += numLinesDelta;
  3365.         if (startLine < itsNumLines)
  3366.             BlockMove(*itsLineStarts + startLine + (-numLinesDelta), *itsLineStarts + startLine, (itsNumLines - startLine) * sizeof(long));
  3367.         SetHandleSize((Handle)itsLineStarts, itsNumLines * sizeof(long));
  3368.         
  3369.         // Bump the values in the remaining entries
  3370.         lineStarts = *itsLineStarts + startLine;
  3371.         count = itsNumLines - startLine + 1;
  3372.         while (--count > 0)
  3373.             *lineStarts++ += numCharsDelta;
  3374.     }
  3375.     
  3376.     // Adjust the bounds rectangle if the number of lines changed
  3377.     if (numLinesDelta != 0)
  3378.         AdjustBounds();
  3379. }
  3380.  
  3381.  
  3382. /******************************************************************************
  3383.  DrawCaret
  3384.  
  3385.         Internal method to draw the text insertion caret.
  3386.  ******************************************************************************/
  3387.  
  3388. void    CPEditText::DrawCaret()
  3389. {
  3390.     LongPt        caretPt;
  3391.     LongRect    caretRect;
  3392.     Rect        qdRect;
  3393.     
  3394.     if (itsSelStart == itsSelEnd) {
  3395.         GetCharPoint(itsSelStart, &caretPt);
  3396.         SetLongRect(&caretRect, caretPt.h - 1, caretPt.v - itsFontAscent, caretPt.h, caretPt.v - itsFontAscent + itsLineHeight);
  3397.         FrameToQDR(&caretRect, &qdRect);
  3398.         CaretHook(&qdRect);
  3399.     }
  3400. }
  3401.  
  3402.  
  3403. /******************************************************************************
  3404.  ShowCaret
  3405.  
  3406.         Internal method to show the text insertion caret if it is not visible.
  3407.  ******************************************************************************/
  3408.  
  3409. void    CPEditText::ShowCaret()
  3410. {
  3411.     if (!fCaretVisible) {
  3412.         DrawCaret();
  3413.         fCaretVisible = TRUE;
  3414.         itsCaretTime = TickCount() + GetCaretTime();
  3415.     }
  3416. }
  3417.  
  3418.  
  3419. /******************************************************************************
  3420.  HideCaret
  3421.  
  3422.         Internal method to hide the text insertion caret if it is visible.
  3423.  ******************************************************************************/
  3424.  
  3425. void    CPEditText::HideCaret()
  3426. {
  3427.     if (fCaretVisible) {
  3428.         DrawCaret();
  3429.         fCaretVisible = FALSE;
  3430.         itsCaretTime = TickCount() + GetCaretTime();
  3431.     }
  3432. }
  3433.  
  3434.  
  3435. /******************************************************************************
  3436.  GetMacFontInfo
  3437.  
  3438.         Return a QuickDraw FontInfo record for the font used by a PEditText
  3439.         pane. This method changes the font family, style, and size of the
  3440.         pane's port so the Toolbox trap GetFontInfo can be used.
  3441.  ******************************************************************************/
  3442.  
  3443. void    CPEditText::GetMacFontInfo(FontInfo *macFontInfo)
  3444. {
  3445.     ForceNextPrepare();
  3446.     
  3447.     SetPort(macPort);
  3448.     TextFont(itsTextFont);
  3449.     TextFace(itsTextFace);
  3450.     TextSize(itsTextSize);
  3451.     GetFontInfo(macFontInfo);
  3452. }
  3453.  
  3454.  
  3455. /**** U T I L I T Y   F U N C T I O N S ****/
  3456.  
  3457.  
  3458. /*============================================================================
  3459.  CountCRs
  3460.  
  3461.          Return the number of carriage return characters in the given text.
  3462.  =============================================================================*/
  3463.  
  3464. static long CountCRs(register Ptr textP, register long numChars)
  3465. {
  3466.     register Byte    cr = kReturn;
  3467.     register long    numCRs = 0;
  3468.     
  3469.     while (numChars--) {
  3470.         if (*textP++ == cr)
  3471.             ++numCRs;
  3472.     }
  3473.     
  3474.     return numCRs;
  3475. }
  3476.  
  3477.  
  3478. /*============================================================================
  3479.  GetGrayRGBColor
  3480.  
  3481.          Get the grayishTextOr gray RGB color.  Return TRUE if the color
  3482.          was retrieved, FALSE otherwise.
  3483.  =============================================================================*/
  3484.  
  3485. static Boolean GetGrayRGBColor(const Rect *localRect, RGBColor *grayColor, RGBColor *prevForeColor)
  3486. {
  3487.     GDHandle    targetDevice;
  3488.     Rect        globalRect;
  3489.     RGBColor    backColor;
  3490.         
  3491.     if (gSystem.hasColorQD && IsColorPort(thePort) && GestaltHasAttr(gestaltQuickdrawFeatures, gestaltHasGrayishTextOr)) {
  3492.         GetForeColor(prevForeColor);
  3493.         GetBackColor(&backColor);
  3494.         globalRect = *localRect;
  3495.         LocalToGlobal(&topLeft(globalRect));
  3496.         LocalToGlobal(&botRight(globalRect));
  3497.         targetDevice = GetMaxDevice(&globalRect);
  3498.         *grayColor = *prevForeColor;
  3499.  
  3500.         return GetGray(targetDevice, &backColor, grayColor);
  3501.     }
  3502.     else
  3503.         return FALSE;
  3504. }
  3505.  
  3506.  
  3507. /*============================================================================
  3508.  GestaltHasAttr
  3509.  
  3510.          Call Gestalt with the given selector and test the given bit in
  3511.          the response.
  3512.  =============================================================================*/
  3513.  
  3514. static Boolean    GestaltHasAttr(OSType selector, short responseBit)
  3515. {
  3516.     long        response;
  3517.     
  3518.     return (Gestalt(selector, &response) == noErr && (response & (1L << responseBit)) != 0);
  3519. }
  3520.